diff --git a/Makefile.am b/Makefile.am index 697ce342f7..0ba1eeefb5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -92,6 +92,7 @@ distcheck-light-man: # Make a distcheck (and check in particular) with enabled valgrind and debug info memcheck distcheck-valgrind: + @echo "See also scripts/valgrind in NUT sources for a helper tool" +$(MAKE) $(AM_MAKEFLAGS) DISTCHECK_FLAGS="$(DISTCHECK_VALGRIND_FLAGS)" distcheck # workaround the dist generated files that are also part of the distribution diff --git a/NEWS.adoc b/NEWS.adoc index e674d73787..65830dd276 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -151,6 +151,8 @@ installed. subnets with too many bits available for the host address part (avoiding millions of scans in the extreme cases). [issue #2244, issue #2511, PR #2509, PR #2513, PR #2517] + * implemented parallel scanning for IPMI bus, otherwise default scan for + all supported buses with `-m auto` takes unbearably long. [#2523] * bumped version of `libnutscan` to 2.5.2, it now includes a few more methods and symbols from `libcommon`. [issue #2244, PR #2509] @@ -182,6 +184,9 @@ installed. respective warnings issued by the new generations of analysis tools. [#823, #2437, link:https://github.com/networkupstools/nut-website/issues/52[nut-website issue #52]] + - added `scripts/valgrind` with a helper script and suppression file to + ignore common third-party problems. [#2511] + - revised `nut.exe` (the NUT for Windows wrapper for all-in-one service) to be more helpful with command-line use (report that it failed to start as a service, have a help message, pass debug verbosity to launched NUT diff --git a/UPGRADING.adoc b/UPGRADING.adoc index ae9612c6df..1b8fae5857 100644 --- a/UPGRADING.adoc +++ b/UPGRADING.adoc @@ -43,8 +43,8 @@ Changes from 2.8.2 to 2.8.3 - Numerous changes to `nut-scanner` and symbols that its `libnutscan.so` delivers have caused a library version bump. New methods have been added - in a (hopefully) backwards compatible manner. [issue #2244 and numerous - PRs for it] + and one structure (`nutscan_ipmi_t`) updated in a (hopefully) backwards + compatible manner. [PR #2523, issue #2244 and numerous PRs for it] - Internal API change for `sendsignalpid()` and `sendsignalfn()` methods, which can impact NUT forks which build using `libcommon.la` and similar diff --git a/common/common.c b/common/common.c index f7ffee93d9..a6646de03f 100644 --- a/common/common.c +++ b/common/common.c @@ -3087,6 +3087,9 @@ void nut_prepare_search_paths(void) { dupe = 1; #if HAVE_DECL_REALPATH free((char *)dirname); + /* Have some valid value, for kicks (likely + * to be ignored in the code path below) */ + dirname = search_paths_builtin[i]; #endif break; } @@ -3097,10 +3100,17 @@ void nut_prepare_search_paths(void) { "existing unique directory: %s", __func__, count_filtered, dirname); #if !HAVE_DECL_REALPATH + /* Make a copy of table entry, else we have + * a dynamic result of realpath() made above. + */ dirname = (const char *)xstrdup(dirname); #endif filtered_search_paths[count_filtered++] = dirname; - } + } /* else: dirname was freed above (for realpath) + * or is a reference to the table entry; no need + * to free() it either way */ + + closedir(dp); } /* If we mangled this before, forget the old result: */ diff --git a/docs/developers.txt b/docs/developers.txt index 6c95549140..ccddee5be2 100644 --- a/docs/developers.txt +++ b/docs/developers.txt @@ -1212,6 +1212,9 @@ suspected area and then exit cleanly. valgrind will tell you if you've done anything dodgy like freeing regions twice, reading uninitialized memory, or if you've leaked memory anywhere. +See also `scripts/valgrind` in NUT sources for a helper tool and resource +files to suppress common third-party problems. + For more information, refer to the link:http://valgrind.kde.org[Valgrind] project. diff --git a/docs/nut.dict b/docs/nut.dict index 6bb3967ff3..bf82d3a5ad 100644 --- a/docs/nut.dict +++ b/docs/nut.dict @@ -1,4 +1,4 @@ -personal_ws-1.1 en 3186 utf-8 +personal_ws-1.1 en 3188 utf-8 AAC AAS ABI @@ -2594,6 +2594,7 @@ pts pty pulizzi pw +pwd pwmib px pxW @@ -2859,6 +2860,7 @@ subnets subtree sudo suid +suppressions suseconds sv svc diff --git a/scripts/Makefile.am b/scripts/Makefile.am index 225c10f015..f777ee9073 100644 --- a/scripts/Makefile.am +++ b/scripts/Makefile.am @@ -25,12 +25,15 @@ EXTRA_DIST = \ upower/95-upower-hid.rules \ usb_resetter/README.adoc \ usb_resetter/nut-driver.service \ + valgrind/README.adoc \ + valgrind/.valgrind.supp \ + valgrind/valgrind.sh \ Windows/halt.c \ Windows/Makefile SUBDIRS = augeas devd hotplug installer python systemd udev ufw Solaris Windows upsdrvsvcctl -SPELLCHECK_SRC = README.adoc RedHat/README.adoc usb_resetter/README.adoc +SPELLCHECK_SRC = README.adoc RedHat/README.adoc usb_resetter/README.adoc valgrind/README.adoc # NOTE: Due to portability, we do not use a GNU percent-wildcard extension. # We also have to export some variables that may be tainted by relative diff --git a/scripts/valgrind/.valgrind.supp b/scripts/valgrind/.valgrind.supp new file mode 100644 index 0000000000..2e175ebaf5 --- /dev/null +++ b/scripts/valgrind/.valgrind.supp @@ -0,0 +1,142 @@ +{ + + Memcheck:Param + socketcall.send(msg) + fun:send + ... +} +{ + + Memcheck:Free + fun:free + ... + fun:__libc_freeres + ... +} +{ + + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:pool + fun:__static_initialization_and_destruction_0 + fun:_GLOBAL__sub_I_eh_alloc.cc + ... +} +{ + + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:_gcry_mpi_init + fun:global_init + ... +} +{ + + Memcheck:Leak + ... + fun:CRYPTO_* + ... +} +{ + + Memcheck:Leak + ... + fun:SSL_CTX_* + ... +} +{ + + Memcheck:Leak + ... + fun:OPENSSL_* + ... +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:_dl_init + ... +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:_dl_new_object + ... +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:elf_machine_rela + ... +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:dl_open_worker + ... +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:_dlerror_run + ... +} +{ + + Memcheck:Leak + ... + fun:init_snmp_once + ... +} +{ + + Memcheck:Leak + fun:*alloc + ... + fun:wrap_nut_snmp_sess_open + ... +} +{ + + Memcheck:Leak + ... + fun:wrap_nut_ipmi_ctx_create + ... +} +{ + + Memcheck:Leak + ... + fun:wrap_nut_avahi_client_new + ... +} +{ + # https://forums.freebsd.org/threads/named-semaphore-uninitialized-bytes.84850/ + + Memcheck:Cond + ... + fun:sem_open + ... +} +{ + + Memcheck:Cond + ... + fun:sem_trywait* + ... + fun:start_thread + ... +} diff --git a/scripts/valgrind/README.adoc b/scripts/valgrind/README.adoc new file mode 100644 index 0000000000..6d5866163e --- /dev/null +++ b/scripts/valgrind/README.adoc @@ -0,0 +1,16 @@ +VALGRIND resources +================== + +Helper script and suppression file to analyze NUT binaries. + +Example use-case: +---- +:; make -ks -j && LD_LIBRARY_PATH=`pwd`/clients/.libs \ + ./scripts/valgrind/valgrind.sh ./tools/nut-scanner/nut-scanner -DDDDDD -m auto +---- + +See also: + +* link:https://wiki.wxwidgets.org/Valgrind_Suppression_File_Howto[Valgrind Suppression File How-to] + - Notably, add `--gen-suppressions=all --error-limit=no` to `valgrind` + program options to generate suppression snippets diff --git a/scripts/valgrind/valgrind.sh b/scripts/valgrind/valgrind.sh new file mode 100755 index 0000000000..2bb8608125 --- /dev/null +++ b/scripts/valgrind/valgrind.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +# Copyright (C) 2024 by Jim Klimov + +SCRIPTDIR="`dirname "$0"`" +SCRIPTDIR="`cd "$SCRIPTDIR" && pwd`" + +LTEXEC="" +[ -n "${LIBTOOL-}" ] || LIBTOOL="`command -v libtool`" 2>/dev/null +[ -z "${LIBTOOL}" ] || LTEXEC="${LIBTOOL} --mode=execute " + +# TODO: Also wrap tool=callgrind e.g. via $0 symlink name? + +exec ${LTEXEC} \ +valgrind \ + --tool=memcheck --verbose \ + --leak-check=full --show-reachable=yes --error-exitcode=1 --show-error-list=yes \ + --trace-children=yes --track-fds=yes --show-leak-kinds=all --track-origins=yes \ + --suppressions="${SCRIPTDIR}/.valgrind.supp" \ + ${VALGRIND_OPTIONS-} \ + "$@" diff --git a/tools/nut-scanner/Makefile.am b/tools/nut-scanner/Makefile.am index a445acc3d1..301c593656 100644 --- a/tools/nut-scanner/Makefile.am +++ b/tools/nut-scanner/Makefile.am @@ -96,7 +96,7 @@ libnutscan_la_LDFLAGS += -version-info 2:5:2 # copies of "nut_debug_level" making fun of our debug-logging attempts. # One solution to tackle if needed for those cases would be to make some # dynamic/shared libnutcommon (etc.) -libnutscan_la_LDFLAGS += -export-symbols-regex '^(nutscan_|nut_debug_level|s_upsdebug|fatalx|xcalloc|snprintfcat|max_threads|curr_threads|nut_report_config_flags|upsdebugx_report_search_paths|nut_prepare_search_paths)' +libnutscan_la_LDFLAGS += -export-symbols-regex '^(nutscan_|nut_debug_level|s_upsdebug|fatalx|fatal_with_errno|xcalloc|snprintfcat|max_threads|curr_threads|nut_report_config_flags|upsdebugx_report_search_paths|nut_prepare_search_paths)' libnutscan_la_CFLAGS = -I$(top_srcdir)/clients -I$(top_srcdir)/include \ $(LIBLTDL_CFLAGS) -I$(top_srcdir)/drivers diff --git a/tools/nut-scanner/nut-scan.h b/tools/nut-scanner/nut-scan.h index f87a9e8206..22d951bd8b 100644 --- a/tools/nut-scanner/nut-scan.h +++ b/tools/nut-scanner/nut-scan.h @@ -83,7 +83,7 @@ extern "C" { #ifdef HAVE_PTHREAD # if (defined HAVE_PTHREAD_TRYJOIN) || (defined HAVE_SEMAPHORE) -extern size_t max_threads, curr_threads, max_threads_netxml, max_threads_oldnut, max_threads_netsnmp; +extern size_t max_threads, curr_threads, max_threads_netxml, max_threads_oldnut, max_threads_netsnmp, max_threads_ipmi; # endif # ifdef HAVE_PTHREAD_TRYJOIN @@ -110,16 +110,17 @@ typedef struct nutscan_snmp { } nutscan_snmp_t; /* IPMI structure */ -/* Settings for OutofBand (remote) connection */ +/* Settings for Out-of-Band (remote) connection */ typedef struct nutscan_ipmi { - char* username; /* IPMI 1.5 and 2.0 */ - char* password; /* IPMI 1.5 and 2.0 */ - int authentication_type; /* IPMI 1.5 */ - int cipher_suite_id; /* IPMI 2.0 */ - char* K_g_BMC_key; /* IPMI 2.0, optional key for 2 key auth. */ - int privilege_level; /* for both */ + char* username; /* IPMI 1.5 and 2.0 */ + char* password; /* IPMI 1.5 and 2.0 */ + int authentication_type; /* IPMI 1.5 */ + int cipher_suite_id; /* IPMI 2.0 */ + char* K_g_BMC_key; /* IPMI 2.0, optional key for 2 key auth. */ + int privilege_level; /* for both */ unsigned int workaround_flags; /* for both */ - int ipmi_version; /* IPMI 1.5 or 2.0? */ + int ipmi_version; /* IPMI 1.5 or 2.0? */ + char* peername; /* Hostname or IP for remote scans, NULL for local device bus (populated by scanning methods) */ } nutscan_ipmi_t; /* IPMI auth defines, simply using FreeIPMI defines */ diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index 2e664f6ce1..ebfacc9aa7 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -348,7 +348,7 @@ static void handle_arg_cidr(const char *arg_addr, int *auto_nets_ptr) * `-m auto4/X` and `-m auto6/Y` requests? */ if (auto_nets_ptr && *auto_nets_ptr) { - fprintf(stderr, "Duplicate request for connected subnet scan ignored\n"); + upsdebugx(0, "Duplicate request for connected subnet scan ignored"); return; } @@ -437,9 +437,8 @@ static void handle_arg_cidr(const char *arg_addr, int *auto_nets_ptr) if (getifaddrs(&ifap) < 0 || !ifap) { if (ifap) freeifaddrs(ifap); - fatalx(EXIT_FAILURE, - "Failed to getifaddrs() for connected subnet scan: %s\n", - strerror(errno)); + fatal_with_errno(EXIT_FAILURE, + "Failed to getifaddrs() for connected subnet scan"); /* TOTHINK: Non-fatal, just return / goto finish? * Either way, do not proceed with code below! */ } @@ -1175,7 +1174,7 @@ int main(int argc, char *argv[]) /* Get max number of files. */ if (getrlimit(RLIMIT_NOFILE, &nofile_limit) != 0) { /* Report error, keep hardcoded default */ - fprintf(stderr, "getrlimit() failed with errno=%d, keeping default job limits\n", errno); + upsdebug_with_errno(0, "getrlimit() failed, keeping default job limits"); nofile_limit.rlim_cur = 0; nofile_limit.rlim_max = 0; } else { @@ -1201,6 +1200,7 @@ int main(int argc, char *argv[]) ipmi_sec.ipmi_version = IPMI_1_5; /* default to IPMI 1.5, if not otherwise specified */ ipmi_sec.cipher_suite_id = 3; /* default to HMAC-SHA1; HMAC-SHA1-96; AES-CBC-128 */ ipmi_sec.privilege_level = IPMI_PRIVILEGE_LEVEL_ADMIN; /* should be sufficient */ + ipmi_sec.peername = NULL; /* Set the default values for XML HTTP (run_xml()) */ xml_sec.port_http = 80; @@ -1248,8 +1248,8 @@ int main(int argc, char *argv[]) if (errno_saved || (s && *s != '\0') || l <= 0) { /* TODO: Any max limit? At least, * max(useconds_t)/1000000 ? */ - fprintf(stderr, - "Illegal timeout value, using default %ds\n", + upsdebugx(0, + "Illegal timeout value, using default %ds", DEFAULT_NETWORK_TIMEOUT); timeout = DEFAULT_NETWORK_TIMEOUT * 1000 * 1000; } else { @@ -1403,8 +1403,8 @@ int main(int argc, char *argv[]) ipmi_sec.authentication_type = IPMI_AUTHENTICATION_TYPE_MD5; } else { - fprintf(stderr, - "Unknown authentication type (%s). Defaulting to MD5\n", + upsdebugx(0, + "Unknown authentication type (%s). Defaulting to MD5", optarg); } break; @@ -1448,27 +1448,27 @@ int main(int argc, char *argv[]) max_threads -= RESERVE_FD_COUNT; } - fprintf(stderr, + upsdebugx(0, "WARNING: Requested max scanning " "thread count %s (%ld) exceeds the " "current file descriptor count limit " "(minus reservation), constraining " - "to %" PRIuSIZE "\n", + "to %" PRIuSIZE, optarg, val, max_threads); } else # endif /* HAVE_SYS_RESOURCE_H */ max_threads = (size_t)val; } else { - fprintf(stderr, + upsdebugx(0, "WARNING: Requested max scanning " "thread count %s (%ld) is out of range, " - "using default %" PRIuSIZE "\n", + "using default %" PRIuSIZE, optarg, val, max_threads); } #else - fprintf(stderr, + upsdebugx(0, "WARNING: Max scanning thread count option " - "is not supported in this build, ignored\n"); + "is not supported in this build, ignored"); #endif /* HAVE_PTHREAD && ways to limit the thread count */ } break; @@ -1885,8 +1885,8 @@ int main(int argc, char *argv[]) #endif upsdebugx(1, "SCANS DONE: free common scanner resources"); - nutscan_free(); nutscan_free_ip_ranges(&ip_ranges_list); + nutscan_free(); upsdebugx(1, "SCANS DONE: EXIT_SUCCESS"); return EXIT_SUCCESS; diff --git a/tools/nut-scanner/nutscan-display.c b/tools/nut-scanner/nutscan-display.c index 08234f7e8c..f486b99198 100644 --- a/tools/nut-scanner/nutscan-display.c +++ b/tools/nut-scanner/nutscan-display.c @@ -263,7 +263,7 @@ void nutscan_display_sanity_check_serial(nutscan_device_t * device) /* Reserve enough slots for all-unique serials */ map = calloc(listlen, sizeof(keyval_strings_t)); if (map == NULL) { - fprintf(stderr, "%s: Memory allocation error, skipped\n", __func__); + upsdebugx(0, "%s: Memory allocation error, skipped", __func__); return; } @@ -317,7 +317,7 @@ void nutscan_display_sanity_check_serial(nutscan_device_t * device) count++; if (count != (i + 1) || count > listlen) { /* Should never get here, but just in case... */ - fprintf(stderr, "%s: Loop overflow, skipped\n", __func__); + upsdebugx(0, "%s: Loop overflow, skipped", __func__); upsdebugx(3, "%s: count=%" PRIuSIZE " i=%" PRIuSIZE " listlen%" PRIuSIZE, __func__, count, i, listlen); goto exit; diff --git a/tools/nut-scanner/nutscan-init.c b/tools/nut-scanner/nutscan-init.c index 9ef76ad023..a0246f52fc 100644 --- a/tools/nut-scanner/nutscan-init.c +++ b/tools/nut-scanner/nutscan-init.c @@ -41,6 +41,11 @@ # if defined HAVE_WINSOCK2_H && HAVE_WINSOCK2_H # include # endif +#endif + +/* FIXME: We may want to (also?) use lt_dlopenext(), so + * that libtool would offer platform-specific extensions */ +#ifdef WIN32 # define SOEXT ".dll" #else # ifdef NUT_PLATFORM_APPLE_OSX @@ -61,12 +66,19 @@ int nutscan_avail_snmp = 0; int nutscan_avail_usb = 0; int nutscan_avail_xml_http = 0; +/* Methods defined in scan_*.c source files */ int nutscan_load_usb_library(const char *libname_path); +int nutscan_unload_usb_library(void); int nutscan_load_snmp_library(const char *libname_path); +int nutscan_unload_snmp_library(void); int nutscan_load_neon_library(const char *libname_path); +int nutscan_unload_neon_library(void); int nutscan_load_avahi_library(const char *libname_path); +int nutscan_unload_avahi_library(void); int nutscan_load_ipmi_library(const char *libname_path); +int nutscan_unload_ipmi_library(void); int nutscan_load_upsclient_library(const char *libname_path); +int nutscan_unload_upsclient_library(void); #ifdef HAVE_PTHREAD # ifdef HAVE_SEMAPHORE @@ -106,6 +118,7 @@ size_t max_threads_netsnmp = 0; /* 10240; */ * Still, some practical limit can be useful (configurable?) * Here 0 means to not apply any special limit (beside max_threads). */ +size_t max_threads_ipmi = 0; /* limits not yet known */ # endif /* HAVE_PTHREAD_TRYJOIN || HAVE_SEMAPHORE */ @@ -585,27 +598,67 @@ void nutscan_init(void) nutscan_avail_nut_simulation = 1; } -void nutscan_free(void) +/* Return 0 on success, -1 on error e.g. "was not loaded"; + * other values may be possible if lt_dlclose() errors set them; + * visible externally to scan_* modules */ +int nutscan_unload_library(int *avail, lt_dlhandle *pdl_handle, char **libpath); +int nutscan_unload_library(int *avail, lt_dlhandle *pdl_handle, char **libpath) { - if (nutscan_avail_usb) { - lt_dlexit(); + int ret = -1; + + if (avail == NULL || pdl_handle == NULL) { + upsdebugx(1, "%s: called with bad inputs, no-op", __func__); + return -2; } - if (nutscan_avail_snmp) { - lt_dlexit(); + + /* never tried/already unloaded */ + if (*pdl_handle == NULL) { + goto end; } - if (nutscan_avail_xml_http) { - lt_dlexit(); + + /* if previous init failed */ + if (*pdl_handle == (void *)1) { + goto end; } - if (nutscan_avail_avahi) { - lt_dlexit(); + + if (*avail == 0) { + upsdebugx(1, "%s: Asked to unload a module %p " + "for %s but our flag says it is not loaded", + __func__, (void *)(*pdl_handle), + (libpath && *libpath && **libpath) + ? *libpath + : ""); } - if (nutscan_avail_ipmi) { - lt_dlexit(); + + /* init has already been done */ + if (libpath && *libpath && **libpath) { + upsdebugx(1, "%s: unloading module %s", + __func__, *libpath); } - if (nutscan_avail_nut) { - lt_dlexit(); + ret = lt_dlclose(*pdl_handle); + lt_dlexit(); + +end: + *pdl_handle = NULL; + *avail = 0; + + if (libpath && *libpath) { + free(*libpath); + *libpath = NULL; } + return ret; +} + +void nutscan_free(void) +{ + nutscan_unload_usb_library(); + nutscan_unload_snmp_library(); + nutscan_unload_neon_library(); + nutscan_unload_avahi_library(); + nutscan_unload_ipmi_library(); + nutscan_unload_upsclient_library(); + #ifdef HAVE_PTHREAD /* TOTHINK: See comments near mutex/semaphore init code above */ # ifdef HAVE_SEMAPHORE diff --git a/tools/nut-scanner/nutscan-ip.c b/tools/nut-scanner/nutscan-ip.c index 0f89e430e2..5c09a15134 100644 --- a/tools/nut-scanner/nutscan-ip.c +++ b/tools/nut-scanner/nutscan-ip.c @@ -370,7 +370,8 @@ char * nutscan_ip_iter_init(nutscan_ip_iter_t * ip, const char * startIP, const ip->type = IPv6; hints.ai_family = AF_INET6; if (getaddrinfo(startIP, NULL, &hints, &res) != 0) { - fprintf(stderr, "Invalid address : %s\n", startIP); + upsdebugx(0, "WARNING: %s: Invalid address : %s", + __func__, startIP); return NULL; } @@ -388,7 +389,8 @@ char * nutscan_ip_iter_init(nutscan_ip_iter_t * ip, const char * startIP, const if (ip->type == IPv4) { hints.ai_family = AF_INET; if (getaddrinfo(stopIP, NULL, &hints, &res) != 0) { - fprintf(stderr, "Invalid address : %s\n", stopIP); + upsdebugx(0, "WARNING: %s: Invalid address : %s", + __func__, stopIP); return NULL; } @@ -399,7 +401,8 @@ char * nutscan_ip_iter_init(nutscan_ip_iter_t * ip, const char * startIP, const else { hints.ai_family = AF_INET6; if (getaddrinfo(stopIP, NULL, &hints, &res) != 0) { - fprintf(stderr, "Invalid address : %s\n", stopIP); + upsdebugx(0, "WARNING: %s: Invalid address : %s", + __func__, stopIP); return NULL; } memcpy(s_in6, res->ai_addr, sizeof(struct sockaddr_in6)); diff --git a/tools/nut-scanner/nutscan-serial.c b/tools/nut-scanner/nutscan-serial.c index a98b4dcb29..b354752ada 100644 --- a/tools/nut-scanner/nutscan-serial.c +++ b/tools/nut-scanner/nutscan-serial.c @@ -42,7 +42,7 @@ #define SERIAL_PORT_PREFIX "/dev/tty" #endif -#define ERR_OUT_OF_BOUND "Serial port range out of bound (must be 0 to 9 or a to z depending on your system)\n" +#define ERR_OUT_OF_BOUND "Serial port range out of bound (must be 0 to 9 or a to z depending on your system)" typedef struct { char * name; @@ -108,7 +108,7 @@ static char ** add_port(char ** list, char * port) /*+1 for the terminal NULL */ res = realloc(list, sizeof(char*) * (count + 1 + 1)); if (res == NULL) { - upsdebugx(1, "%s: Failed to realloc port list", __func__); + upsdebugx(0, "%s: Failed to realloc port list", __func__); return list; } res[count] = strdup(port); @@ -146,7 +146,7 @@ char ** nutscan_get_serial_ports_list(const char *ports_range) if ((list_sep_ptr = strchr(range, '-')) != NULL) { tok = strtok_r(range, "-", &saveptr); if (tok[1] != 0) { - fprintf(stderr, ERR_OUT_OF_BOUND); + upsdebugx(0, "%s", ERR_OUT_OF_BOUND); free(range); return NULL; } @@ -154,7 +154,7 @@ char ** nutscan_get_serial_ports_list(const char *ports_range) tok = strtok_r(NULL, "-", &saveptr); if (tok != NULL) { if (tok[1] != 0) { - fprintf(stderr, ERR_OUT_OF_BOUND); + upsdebugx(0, "%s", ERR_OUT_OF_BOUND); free(range); return NULL; } diff --git a/tools/nut-scanner/scan_avahi.c b/tools/nut-scanner/scan_avahi.c index d7813777a4..8a9a4bbe1c 100644 --- a/tools/nut-scanner/scan_avahi.c +++ b/tools/nut-scanner/scan_avahi.c @@ -27,6 +27,9 @@ #include "common.h" #include "nut-scan.h" +/* externally visible to nutscan-init */ +int nutscan_unload_avahi_library(void); + #ifdef WITH_AVAHI #include @@ -48,6 +51,7 @@ /* dynamic link library stuff */ static lt_dlhandle dl_handle = NULL; static const char *dl_error = NULL; +static char *dl_saved_libname = NULL; static AvahiClient* (*nut_avahi_service_browser_get_client)(AvahiServiceBrowser *); static int (*nut_avahi_simple_poll_loop)(AvahiSimplePoll *s); @@ -91,7 +95,16 @@ static int (*nut_avahi_service_browser_free)(AvahiServiceBrowser *); static char * (*nut_avahi_address_snprint)(char *ret_s, size_t length, const AvahiAddress *a); static const AvahiPoll* (*nut_avahi_simple_poll_get)(AvahiSimplePoll *s); -/* return 0 on error; visible externally */ +/* Return 0 on success, -1 on error e.g. "was not loaded"; + * other values may be possible if lt_dlclose() errors set them; + * visible externally */ +int nutscan_unload_library(int *avail, lt_dlhandle *pdl_handle, char **libpath); +int nutscan_unload_avahi_library(void) +{ + return nutscan_unload_library(&nutscan_avail_avahi, &dl_handle, &dl_saved_libname); +} + +/* Return 0 on error; visible externally */ int nutscan_load_avahi_library(const char *libname_path); int nutscan_load_avahi_library(const char *libname_path) { @@ -105,12 +118,12 @@ int nutscan_load_avahi_library(const char *libname_path) } if (libname_path == NULL) { - fprintf(stderr, "AVAHI client library not found. AVAHI search disabled.\n"); + upsdebugx(0, "AVAHI client library not found. AVAHI search disabled."); return 0; } if (lt_dlinit() != 0) { - fprintf(stderr, "Error initializing lt_init\n"); + upsdebugx(0, "%s: Error initializing lt_dlinit", __func__); return 0; } @@ -119,7 +132,10 @@ int nutscan_load_avahi_library(const char *libname_path) dl_error = lt_dlerror(); goto err; } - lt_dlerror(); /* Clear any existing error */ + + /* Clear any existing error */ + lt_dlerror(); + *(void **) (&nut_avahi_service_browser_get_client) = lt_dlsym(dl_handle, "avahi_service_browser_get_client"); if ((dl_error = lt_dlerror()) != NULL) { goto err; @@ -210,13 +226,22 @@ int nutscan_load_avahi_library(const char *libname_path) goto err; } + if (dl_saved_libname) + free(dl_saved_libname); + dl_saved_libname = xstrdup(libname_path); + return 1; + err: - fprintf(stderr, - "Cannot load AVAHI library (%s) : %s. AVAHI search disabled.\n", + upsdebugx(0, + "Cannot load AVAHI library (%s) : %s. AVAHI search disabled.", libname_path, dl_error); dl_handle = (void *)1; lt_dlexit(); + if (dl_saved_libname) { + free(dl_saved_libname); + dl_saved_libname = NULL; + } return 0; } /* end of dynamic link library stuff */ @@ -383,23 +408,23 @@ static void resolve_callback( switch (event) { case AVAHI_RESOLVER_FAILURE: - fprintf(stderr, - "(Resolver) Failed to resolve service '%s' of type '%s' in domain '%s': %s\n", - name, type, domain, + upsdebugx(0, "%s: " + "(Resolver) Failed to resolve service '%s' of type '%s' in domain '%s': %s", + __func__, name, type, domain, (*nut_avahi_strerror)((*nut_avahi_client_errno)((*nut_avahi_service_resolver_get_client)(r)))); break; case AVAHI_RESOLVER_FOUND: { char a[AVAHI_ADDRESS_STR_MAX], *t; -/* fprintf(stderr, "Service '%s' of type '%s' in domain '%s':\n", name, type, domain); */ +/* upsdebugx(1, "%s: Service '%s' of type '%s' in domain '%s':", __func__, name, type, domain); */ (*nut_avahi_address_snprint)(a, sizeof(a), address); t = (*nut_avahi_string_list_to_string)(txt); NUT_UNUSED_VARIABLE(flags); /* - fprintf(stderr, + upsdebugx(1, "\t%s:%u (%s)\n" "\tTXT=%s\n" "\tcookie is %u\n" @@ -447,14 +472,14 @@ static void browse_callback( switch (event) { case AVAHI_BROWSER_FAILURE: - fprintf(stderr, - "(Browser) %s\n", + upsdebugx(0, "%s: (Browser) %s", + __func__, (*nut_avahi_strerror)((*nut_avahi_client_errno)((*nut_avahi_service_browser_get_client)(b)))); (*nut_avahi_simple_poll_quit)(simple_poll); return; case AVAHI_BROWSER_NEW: -/* fprintf(stderr, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain); */ +/* upsdebugx(1, "%s: "(Browser) NEW: service '%s' of type '%s' in domain '%s'", __func__, name, type, domain); */ /* We ignore the returned resolver object. In the callback function we free it. If the server is terminated before @@ -474,14 +499,15 @@ static void browse_callback( #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_ASSIGN_ENUM) # pragma GCC diagnostic pop #endif - fprintf(stderr, - "Failed to resolve service '%s': %s\n", + upsdebugx(0, "%s: " + "Failed to resolve service '%s': %s", + __func__, name, (*nut_avahi_strerror)((*nut_avahi_client_errno)(c))); break; case AVAHI_BROWSER_REMOVE: - fprintf(stderr, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain); + upsdebugx(0, "%s: (Browser) REMOVE: service '%s' of type '%s' in domain '%s'", __func__, name, type, domain); break; case AVAHI_BROWSER_ALL_FOR_NOW: @@ -490,7 +516,7 @@ static void browse_callback( case AVAHI_BROWSER_CACHE_EXHAUSTED: fallthrough_AVAHI_BROWSER_CACHE_EXHAUSTED: -/* fprintf(stderr, "(Browser) %s\n", event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW"); */ +/* upsdebugx(1, "%s: (Browser) %s", __func__, event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW"); */ break; } } @@ -502,13 +528,32 @@ static void client_callback(AvahiClient *c, AvahiClientState state, void * userd /* Called whenever the client or server state changes */ if (state == AVAHI_CLIENT_FAILURE) { - fprintf(stderr, - "Server connection failure: %s\n", + upsdebugx(0, "%s: " + "Server connection failure: %s", + __func__, (*nut_avahi_strerror)((*nut_avahi_client_errno)(c))); (*nut_avahi_simple_poll_quit)(simple_poll); } } +static AvahiClient* wrap_nut_avahi_client_new(int *error) +{ + /* Allocate a new client */ +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_ASSIGN_ENUM) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wassign-enum" +#endif + /* It seems that avahi-common/defs.h only defines the flags in a + * manner similar to bitmask flags to request certain features, + * but lacks a value in that enum for lack of flags (unconstrained + * lookup). So we have to silence a warning here... + */ + return (*nut_avahi_client_new)((*nut_avahi_simple_poll_get)(simple_poll), 0, client_callback, NULL, error); +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_ASSIGN_ENUM) +# pragma GCC diagnostic pop +#endif +} + nutscan_device_t * nutscan_scan_avahi(useconds_t usec_timeout) { /* Example service publication @@ -526,30 +571,19 @@ nutscan_device_t * nutscan_scan_avahi(useconds_t usec_timeout) /* Allocate main loop object */ if (!(simple_poll = (*nut_avahi_simple_poll_new)())) { - fprintf(stderr, "Failed to create Avahi simple poll object.\n"); + upsdebugx(0, "%s: Failed to create Avahi simple poll object.", + __func__); goto fail; } /* Allocate a new client */ -#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_ASSIGN_ENUM) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wassign-enum" -#endif - /* It seems that avahi-common/defs.h only defines the flags in a - * manner similar to bitmask flags to request certain features, - * but lacks a value in that enum for lack of flags (unconstrained - * lookup). So we have to silence a warning here... - */ - client = (*nut_avahi_client_new)((*nut_avahi_simple_poll_get)(simple_poll), 0, client_callback, NULL, &error); -#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_ASSIGN_ENUM) -# pragma GCC diagnostic pop -#endif + client = wrap_nut_avahi_client_new(&error); /* Check wether creating the client object succeeded */ if (!client) { - fprintf(stderr, - "Failed to create Avahi client: %s\n", - (*nut_avahi_strerror)(error)); + upsdebugx(0, "%s: " + "Failed to create Avahi client: %s", + __func__, (*nut_avahi_strerror)(error)); goto fail; } @@ -566,8 +600,9 @@ nutscan_device_t * nutscan_scan_avahi(useconds_t usec_timeout) #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_ASSIGN_ENUM) # pragma GCC diagnostic pop #endif - fprintf(stderr, - "Failed to create Avahi service browser: %s\n", + upsdebugx(0, "%s: " + "Failed to create Avahi service browser: %s", + __func__, (*nut_avahi_strerror)((*nut_avahi_client_errno)(client))); goto fail; } @@ -600,4 +635,8 @@ nutscan_device_t * nutscan_scan_avahi(useconds_t usec_timeout) return NULL; } +int nutscan_unload_avahi_library(void) +{ + return 0; +} #endif /* WITH_AVAHI */ diff --git a/tools/nut-scanner/scan_eaton_serial.c b/tools/nut-scanner/scan_eaton_serial.c index b29bb6de2f..93f95875bf 100644 --- a/tools/nut-scanner/scan_eaton_serial.c +++ b/tools/nut-scanner/scan_eaton_serial.c @@ -380,7 +380,14 @@ static nutscan_device_t * nutscan_scan_eaton_serial_q1(const char* port_name) return dev; } -static void * nutscan_scan_eaton_serial_device(void * port_arg) +/* Wrap calls to actual implementations of nutscan_scan_eaton_serial_shut(), + * nutscan_scan_eaton_serial_xcp() and/or nutscan_scan_eaton_serial_q1() + * which implement the semantics of parallel-able scanning. + * Returns the device entry, updates global dev_ret when a scan is successful. + * DOES NOT FREE the caller's copy of "port_arg", unlike many similar methods + * in other scanners. + */ +static void * nutscan_scan_eaton_serial_device_thready(void * port_arg) { nutscan_device_t * dev = NULL; char* port_name = (char*) port_arg; @@ -396,6 +403,7 @@ static void * nutscan_scan_eaton_serial_device(void * port_arg) } /* Else try UTalk? */ } + return dev; } @@ -546,7 +554,7 @@ nutscan_device_t * nutscan_scan_eaton_serial(const char* ports_range) current_port_name = serial_ports_list[current_port_nb]; #ifdef HAVE_PTHREAD - if (pthread_create(&thread, NULL, nutscan_scan_eaton_serial_device, (void*)current_port_name) == 0) { + if (pthread_create(&thread, NULL, nutscan_scan_eaton_serial_device_thready, (void*)current_port_name) == 0) { nutscan_thread_t *new_thread_array; # ifdef HAVE_PTHREAD_TRYJOIN pthread_mutex_lock(&threadcount_mutex); @@ -571,17 +579,20 @@ nutscan_device_t * nutscan_scan_eaton_serial(const char* ports_range) # endif /* HAVE_PTHREAD_TRYJOIN */ } #else /* if not HAVE_PTHREAD */ - nutscan_scan_eaton_serial_device(current_port_name); + nutscan_scan_eaton_serial_device_thready(current_port_name); #endif /* if HAVE_PTHREAD */ + + /* Prepare the next iteration */ current_port_nb++; } else { /* if not pass -- all slots busy */ #ifdef HAVE_PTHREAD # ifdef HAVE_SEMAPHORE /* Wait for all current scans to complete */ if (thread_array != NULL) { - upsdebugx (2, "%s: Running too many scanning threads, " + upsdebugx (2, "%s: Running too many scanning threads (%" + PRIuSIZE "), " "waiting until older ones would finish", - __func__); + __func__, thread_count); for (i = 0; i < thread_count ; i++) { int ret; if (!thread_array[i].active) { diff --git a/tools/nut-scanner/scan_ipmi.c b/tools/nut-scanner/scan_ipmi.c index 6f9d7fc685..316f15bd15 100644 --- a/tools/nut-scanner/scan_ipmi.c +++ b/tools/nut-scanner/scan_ipmi.c @@ -26,10 +26,13 @@ #include "common.h" #include "nut-scan.h" +#include "nut_stdint.h" + +/* externally visible to nutscan-init */ +int nutscan_unload_ipmi_library(void); #ifdef WITH_IPMI -#include "upsclient.h" #include #include #include @@ -45,6 +48,7 @@ /* dynamic link library stuff */ static lt_dlhandle dl_handle = NULL; static const char *dl_error = NULL; +static char *dl_saved_libname = NULL; #ifdef HAVE_FREEIPMI_11X_12X /* Functions symbols remapping */ @@ -115,8 +119,32 @@ static char * (*nut_ipmi_ctx_errormsg) (ipmi_ctx_t ctx); static int (*nut_ipmi_ctx_close) (ipmi_ctx_t ctx); static void (*nut_ipmi_ctx_destroy) (ipmi_ctx_t ctx); +/* This variable collects device(s) from a sequential or parallel scan, + * is returned to caller, and cleared to allow subsequent independent scans */ +static nutscan_device_t * dev_ret = NULL; +#ifdef HAVE_PTHREAD +static pthread_mutex_t dev_mutex; +#endif + +/* use explicit booleans */ +#ifndef FALSE +typedef enum ebool { FALSE = 0, TRUE } bool_t; +#else +typedef int bool_t; +#endif + /* Internal functions */ static nutscan_device_t * nutscan_scan_ipmi_device(const char * IPaddr, nutscan_ipmi_t * sec); +static void * nutscan_scan_ipmi_device_thready(void * arg_sec); + +/* Return 0 on success, -1 on error e.g. "was not loaded"; + * other values may be possible if lt_dlclose() errors set them; + * visible externally */ +int nutscan_unload_library(int *avail, lt_dlhandle *pdl_handle, char **libpath); +int nutscan_unload_ipmi_library(void) +{ + return nutscan_unload_library(&nutscan_avail_ipmi, &dl_handle, &dl_saved_libname); +} /* Return 0 on error; visible externally */ int nutscan_load_ipmi_library(const char *libname_path); @@ -132,12 +160,12 @@ int nutscan_load_ipmi_library(const char *libname_path) } if (libname_path == NULL) { - fprintf(stderr, "IPMI library not found. IPMI search disabled.\n"); + upsdebugx(0, "IPMI library not found. IPMI search disabled."); return 0; } if (lt_dlinit() != 0) { - fprintf(stderr, "Error initializing lt_init\n"); + upsdebugx(0, "%s: Error initializing lt_dlinit", __func__); return 0; } @@ -245,13 +273,22 @@ int nutscan_load_ipmi_library(const char *libname_path) goto err; } + if (dl_saved_libname) + free(dl_saved_libname); + dl_saved_libname = xstrdup(libname_path); + return 1; + err: - fprintf(stderr, - "Cannot load IPMI library (%s) : %s. IPMI search disabled.\n", + upsdebugx(0, + "Cannot load IPMI library (%s) : %s. IPMI search disabled.", libname_path, dl_error); dl_handle = (void *)1; lt_dlexit(); + if (dl_saved_libname) { + free(dl_saved_libname); + dl_saved_libname = NULL; + } return 0; } /* end of dynamic link library stuff */ @@ -308,7 +345,9 @@ static int is_ipmi_device_supported(ipmi_ctx_t ipmi_ctx, int ipmi_id) /* Parse FRU information */ if (!(fru_parse_ctx = (*nut_ipmi_fru_ctx_create) (ipmi_ctx))) { - fprintf(stderr, "Error with %s(): %s\n", IPMI_FRU_CTX_CREATE, (*nut_ipmi_ctx_errormsg)(ipmi_ctx)); + upsdebugx(0, "%s: Error with %s(): %s", + __func__, IPMI_FRU_CTX_CREATE, + (*nut_ipmi_ctx_errormsg)(ipmi_ctx)); return 0; } @@ -324,7 +363,8 @@ static int is_ipmi_device_supported(ipmi_ctx_t ipmi_ctx, int ipmi_id) } if (ipmi_id < 0 || (unsigned int)ipmi_id > UINT8_MAX) { - fprintf(stderr, "is_ipmi_device_supported: ipmi_id=%d is out of range!\n", ipmi_id); + upsdebugx(0, "%s: ipmi_id=%d is out of range!", + __func__, ipmi_id); return 0; } if ((*nut_ipmi_fru_open_device_id) (fru_parse_ctx, (uint8_t)ipmi_id) < 0) @@ -383,6 +423,11 @@ static int is_ipmi_device_supported(ipmi_ctx_t ipmi_ctx, int ipmi_id) return 0; } +static ipmi_ctx_t wrap_nut_ipmi_ctx_create(void) +{ + return (*nut_ipmi_ctx_create) (); +} + /* Check for IPMI support on a specific (local or remote) system * Return NULL on error, or a valid nutscan_device_t otherwise */ nutscan_device_t * nutscan_scan_ipmi_device(const char * IPaddr, nutscan_ipmi_t * ipmi_sec) @@ -399,10 +444,10 @@ nutscan_device_t * nutscan_scan_ipmi_device(const char * IPaddr, nutscan_ipmi_t } /* Initialize the FreeIPMI library. */ - if (!(ipmi_ctx = (*nut_ipmi_ctx_create) ())) + if (!(ipmi_ctx = wrap_nut_ipmi_ctx_create())) { /* we have to force cleanup, since exit handler is not yet installed */ - fprintf(stderr, "Failed to ipmi_ctx_create\n"); + upsdebugx(0, "%s: Failed to ipmi_ctx_create", __func__); return NULL; } @@ -413,7 +458,7 @@ nutscan_device_t * nutscan_scan_ipmi_device(const char * IPaddr, nutscan_ipmi_t /* FIXME: we need root right to access local IPMI! if (!ipmi_is_root ()) { - fprintf(stderr, "IPMI scan: %s\n", ipmi_ctx_strerror (IPMI_ERR_PERMISSION)); + upsdebugx(0, "%s: IPMI scan: %s", __func__, ipmi_ctx_strerror (IPMI_ERR_PERMISSION)); } */ if ((ret = (*nut_ipmi_ctx_find_inband) (ipmi_ctx, @@ -447,7 +492,7 @@ nutscan_device_t * nutscan_scan_ipmi_device(const char * IPaddr, nutscan_ipmi_t * int parse_kg (void *out, unsigned int outlen, const char *in) * if ((rv = parse_kg (common_cmd_args_config->k_g, IPMI_MAX_K_G_LENGTH + 1, data->string)) < 0) * { - * fprintf (stderr, "Config File Error: k_g input formatted incorrectly\n"); + * upsdebugx(0, "%s: Config File Error: k_g input formatted incorrectly", __func__); * exit (EXIT_FAILURE); * }*/ if ((ret = (*nut_ipmi_ctx_open_outofband_2_0) (ipmi_ctx, @@ -563,7 +608,7 @@ nutscan_device_t * nutscan_scan_ipmi_device(const char * IPaddr, nutscan_ipmi_t if (is_ipmi_device_supported(ipmi_ctx, ipmi_id)) { if ((nut_dev = nutscan_new_device()) == NULL) { - fprintf(stderr, "Memory allocation error\n"); + upsdebugx(0, "%s: Memory allocation error", __func__); nutscan_free_device(current_nut_dev); break; } @@ -590,7 +635,6 @@ nutscan_device_t * nutscan_scan_ipmi_device(const char * IPaddr, nutscan_ipmi_t memset (port_id, 0, sizeof(port_id)); } - } /* Final cleanup */ @@ -602,6 +646,40 @@ nutscan_device_t * nutscan_scan_ipmi_device(const char * IPaddr, nutscan_ipmi_t return current_nut_dev; } +/* Wrap calls to nutscan_scan_ipmi_device() into semantics for parallel-able + * scanning. Returns NULL, updates global dev_ret when a scan is successful. + * FREES the caller's copy of "sec" and "peername" in it, if applicable. + */ +static void * nutscan_scan_ipmi_device_thready(void * arg_sec) +{ + nutscan_device_t *dev = NULL; + nutscan_ipmi_t *sec = (nutscan_ipmi_t *)arg_sec; + + if (sec == NULL || sec->peername == NULL) + dev = nutscan_scan_ipmi_device(NULL, NULL); + else + dev = nutscan_scan_ipmi_device(sec->peername, sec); + + if (dev) { +#ifdef HAVE_PTHREAD + pthread_mutex_lock(&dev_mutex); +#endif + dev_ret = nutscan_add_device_to_device(dev_ret, dev); +#ifdef HAVE_PTHREAD + pthread_mutex_unlock(&dev_mutex); +#endif + } + + if (sec) { + if (sec->peername) { + free(sec->peername); + } + free(sec); + } + + return NULL; +} + /* General IPMI scan entry point: scan 1 to n devices, local or remote, * for IPMI support * Return NULL on error, or a valid nutscan_device_t otherwise */ @@ -635,27 +713,42 @@ nutscan_device_t * nutscan_scan_ipmi(const char * start_ip, const char * stop_ip * Return NULL on error, or a valid nutscan_device_t otherwise */ nutscan_device_t * nutscan_scan_ip_range_ipmi(nutscan_ip_range_list_t * irl, nutscan_ipmi_t * sec) { - nutscan_ip_range_list_iter_t ip; - char * ip_str = NULL; - nutscan_ipmi_t * tmp_sec; - nutscan_device_t * nut_dev = NULL; - nutscan_device_t * current_nut_dev = NULL; + bool_t pass = TRUE; /* Track that we may spawn a scanning thread */ + nutscan_device_t * result = NULL; + nutscan_ipmi_t * tmp_sec = NULL; if (!nutscan_avail_ipmi) { return NULL; } - /* Are we scanning locally, or through the network? */ - if (irl == NULL || irl->ip_ranges == NULL) - { + /* Are we scanning locally, or through the network? + * We assume the list is maintained by our methods, so should not have + * null addresses. But just in case - check for it a little tiny once. + */ + if (irl == NULL || irl->ip_ranges == NULL + || irl->ip_ranges->start_ip == NULL || irl->ip_ranges->end_ip == NULL + ) { upsdebugx(1, "%s: Local PSU scan", __func__); - current_nut_dev = nutscan_scan_ipmi_device(NULL, NULL); - } - else { - /* TODO: Port HAVE_PTHREAD_TRYJOIN etc. from other files? - * Notably, the scans below currently are only sequential - * and so very slow (5 sec per IP timeout by default). - */ + nutscan_scan_ipmi_device_thready(NULL); + } else { + /* Iterate the one or a range of IPs to scan */ + nutscan_ip_range_list_iter_t ip; + char * ip_str = NULL; + +#ifdef HAVE_PTHREAD +# ifdef HAVE_SEMAPHORE + sem_t * semaphore = nutscan_semaphore(); + sem_t semaphore_scantype_inst; + sem_t * semaphore_scantype = &semaphore_scantype_inst; +# endif /* HAVE_SEMAPHORE */ + pthread_t thread; + nutscan_thread_t * thread_array = NULL; + size_t thread_count = 0, i; +# if (defined HAVE_PTHREAD_TRYJOIN) || (defined HAVE_SEMAPHORE) + size_t max_threads_scantype = max_threads_ipmi; +# endif +#endif + if (irl->ip_ranges_count == 1 && (irl->ip_ranges->start_ip == irl->ip_ranges->end_ip || !strcmp(irl->ip_ranges->start_ip, irl->ip_ranges->end_ip) @@ -666,22 +759,300 @@ nutscan_device_t * nutscan_scan_ip_range_ipmi(nutscan_ip_range_list_t * irl, nut upsdebugx(1, "%s: Scanning remote PSU for IP address range(s): %s", __func__, nutscan_stringify_ip_ranges(irl)); } + +#ifdef HAVE_PTHREAD + pthread_mutex_init(&dev_mutex, NULL); + +# ifdef HAVE_SEMAPHORE + if (max_threads_scantype > 0) { +#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +#pragma GCC diagnostic push +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +#pragma GCC diagnostic ignored "-Wunreachable-code" +#endif +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif + /* Different platforms, different sizes, none fits all... */ + if (SIZE_MAX > UINT_MAX && max_threads_scantype > UINT_MAX) { +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +#pragma GCC diagnostic pop +#endif + upsdebugx(1, + "WARNING: %s: Limiting max_threads_scantype to range acceptable for sem_init()", + __func__); + max_threads_scantype = UINT_MAX - 1; + } + + upsdebugx(4, "%s: sem_init() for %" PRIuSIZE " threads", __func__, max_threads_scantype); + if (sem_init(semaphore_scantype, 0, (unsigned int)max_threads_scantype)) { + upsdebug_with_errno(4, "%s: sem_init() failed", __func__); + } + } +# endif /* HAVE_SEMAPHORE */ + +#endif /* HAVE_PTHREAD */ + ip_str = nutscan_ip_ranges_iter_init(&ip, irl); while (ip_str != NULL) { - tmp_sec = malloc(sizeof(nutscan_ipmi_t)); - memcpy(tmp_sec, sec, sizeof(nutscan_ipmi_t)); +#ifdef HAVE_PTHREAD + /* NOTE: With many enough targets to scan, this can crash + * by spawning too many children; add a limit and loop to + * "reap" some already done with their work. And probably + * account them in thread_array[] as something to not wait + * for below in pthread_join()... + */ + +# ifdef HAVE_SEMAPHORE + /* Just wait for someone to free a semaphored slot, + * if none are available, and then/otherwise grab one + */ + if (thread_array == NULL) { + /* Starting point, or after a wait to complete + * all earlier runners */ + if (max_threads_scantype > 0) + sem_wait(semaphore_scantype); + sem_wait(semaphore); + pass = TRUE; + } else { + /* If successful (the lock was acquired), + * sem_wait() and sem_trywait() will return 0. + * Otherwise, -1 is returned and errno is set, + * and the state of the semaphore is unchanged. + */ + int stwST = sem_trywait(semaphore_scantype); + int stwS = sem_trywait(semaphore); + pass = ((max_threads_scantype == 0 || stwST == 0) && stwS == 0); + upsdebugx(4, "%s: max_threads_scantype=%" PRIuSIZE + " curr_threads=%" PRIuSIZE + " thread_count=%" PRIuSIZE + " stwST=%d stwS=%d pass=%d", + __func__, max_threads_scantype, + curr_threads, thread_count, + stwST, stwS, pass + ); + } +# else +# ifdef HAVE_PTHREAD_TRYJOIN + /* A somewhat naive and brute-force solution for + * systems without a semaphore.h. This may suffer + * some off-by-one errors, using a few more threads + * than intended (if we race a bit at the wrong time, + * probably up to one per enabled scanner routine). + */ + + /* TOTHINK: Should there be a threadcount_mutex when + * we just read the value in if() and while() below? + * At worst we would overflow the limit a bit due to + * other protocol scanners... + */ + if (curr_threads >= max_threads + || (curr_threads >= max_threads_scantype && max_threads_scantype > 0) + ) { + upsdebugx(2, "%s: already running %" PRIuSIZE " scanning threads " + "(launched overall: %" PRIuSIZE "), " + "waiting until some would finish", + __func__, curr_threads, thread_count); + + while (curr_threads >= max_threads + || (curr_threads >= max_threads_scantype && max_threads_scantype > 0) + ) { + for (i = 0; i < thread_count ; i++) { + int ret; + + if (!thread_array[i].active) continue; + + pthread_mutex_lock(&threadcount_mutex); + upsdebugx(3, "%s: Trying to join thread #%i...", __func__, i); + ret = pthread_tryjoin_np(thread_array[i].thread, NULL); + switch (ret) { + case ESRCH: /* No thread with the ID thread could be found - already "joined"? */ + upsdebugx(5, "%s: Was thread #%" PRIuSIZE " joined earlier?", __func__, i); + break; + case 0: /* thread exited */ + if (curr_threads > 0) { + curr_threads --; + upsdebugx(4, "%s: Joined a finished thread #%" PRIuSIZE, __func__, i); + } else { + /* threadcount_mutex fault? */ + upsdebugx(0, "WARNING: %s: Accounting of thread count " + "says we are already at 0", __func__); + } + thread_array[i].active = FALSE; + break; + case EBUSY: /* actively running */ + upsdebugx(6, "%s: thread #%" PRIuSIZE " still busy (%i)", + __func__, i, ret); + break; + case EDEADLK: /* Errors with thread interactions... bail out? */ + case EINVAL: /* Errors with thread interactions... bail out? */ + default: /* new pthreads abilities? */ + upsdebugx(5, "%s: thread #%" PRIuSIZE " reported code %i", + __func__, i, ret); + break; + } + pthread_mutex_unlock(&threadcount_mutex); + } + + if (curr_threads >= max_threads + || (curr_threads >= max_threads_scantype && max_threads_scantype > 0) + ) { + usleep (10000); /* microSec's, so 0.01s here */ + } + } + upsdebugx(2, "%s: proceeding with scan", __func__); + } - if ((current_nut_dev = nutscan_scan_ipmi_device(ip_str, tmp_sec)) != NULL) { - /* Store the positive result */ - current_nut_dev = nutscan_add_device_to_device(current_nut_dev, nut_dev); + /* NOTE: No change to default "pass" in this ifdef: + * if we got to this line, we have a slot to use */ +# endif /* HAVE_PTHREAD_TRYJOIN */ +# endif /* HAVE_SEMAPHORE */ +#endif /* HAVE_PTHREAD */ + + if (pass) { + tmp_sec = malloc(sizeof(nutscan_ipmi_t)); + if (tmp_sec == NULL) { + upsdebugx(0, "%s: Memory allocation error", __func__); + break; + } + + memcpy(tmp_sec, sec, sizeof(nutscan_ipmi_t)); + tmp_sec->peername = ip_str; + +#ifdef HAVE_PTHREAD + if (pthread_create(&thread, NULL, nutscan_scan_ipmi_device_thready, (void*)tmp_sec) == 0) { + nutscan_thread_t *new_thread_array; +# ifdef HAVE_PTHREAD_TRYJOIN + pthread_mutex_lock(&threadcount_mutex); + curr_threads++; +# endif /* HAVE_PTHREAD_TRYJOIN */ + + thread_count++; + new_thread_array = realloc(thread_array, + thread_count * sizeof(nutscan_thread_t)); + if (new_thread_array == NULL) { + upsdebugx(1, "%s: Failed to realloc thread array", __func__); + break; + } + else { + thread_array = new_thread_array; + } + thread_array[thread_count - 1].thread = thread; + thread_array[thread_count - 1].active = TRUE; + +# ifdef HAVE_PTHREAD_TRYJOIN + pthread_mutex_unlock(&threadcount_mutex); +# endif /* HAVE_PTHREAD_TRYJOIN */ + } +#else /* if not HAVE_PTHREAD */ + nutscan_scan_ipmi_device_thready(tmp_sec); +#endif /* if HAVE_PTHREAD */ + + /* Prepare the next iteration; note that + * nutscan_scan_ipmi_device_thready() + * takes care of freeing "tmp_sec" and its + * reference (NOT strdup!) to "ip_str" as + * peername. + */ + ip_str = nutscan_ip_ranges_iter_inc(&ip); + } else { /* if not pass -- all slots busy */ +#ifdef HAVE_PTHREAD +# ifdef HAVE_SEMAPHORE + /* Wait for all current scans to complete */ + if (thread_array != NULL) { + upsdebugx (2, "%s: Running too many scanning threads (%" + PRIuSIZE "), " + "waiting until older ones would finish", + __func__, thread_count); + for (i = 0; i < thread_count ; i++) { + int ret; + if (!thread_array[i].active) { + /* Probably should not get here, + * but handle it just in case */ + upsdebugx(0, "WARNING: %s: Midway clean-up: did not expect thread %" PRIuSIZE " to be not active", + __func__, i); + sem_post(semaphore); + if (max_threads_scantype > 0) + sem_post(semaphore_scantype); + continue; + } + thread_array[i].active = FALSE; + ret = pthread_join(thread_array[i].thread, NULL); + if (ret != 0) { + upsdebugx(0, "WARNING: %s: Midway clean-up: pthread_join() returned code %i", + __func__, ret); + } + sem_post(semaphore); + if (max_threads_scantype > 0) + sem_post(semaphore_scantype); + } + thread_count = 0; + free(thread_array); + thread_array = NULL; + } +# else +# ifdef HAVE_PTHREAD_TRYJOIN + /* TODO: Move the wait-loop for TRYJOIN here? */ +# endif /* HAVE_PTHREAD_TRYJOIN */ +# endif /* HAVE_SEMAPHORE */ +#endif /* HAVE_PTHREAD */ + } /* if: could we "pass" or not? */ + } /* while */ + +#ifdef HAVE_PTHREAD + if (thread_array != NULL) { + upsdebugx(2, "%s: all planned scans launched, waiting for threads to complete", __func__); + for (i = 0; i < thread_count; i++) { + int ret; + + if (!thread_array[i].active) continue; + + ret = pthread_join(thread_array[i].thread, NULL); + if (ret != 0) { + upsdebugx(0, "WARNING: %s: Clean-up: pthread_join() returned code %i", + __func__, ret); + } + thread_array[i].active = FALSE; +# ifdef HAVE_SEMAPHORE + sem_post(semaphore); + if (max_threads_scantype > 0) + sem_post(semaphore_scantype); +# else +# ifdef HAVE_PTHREAD_TRYJOIN + pthread_mutex_lock(&threadcount_mutex); + if (curr_threads > 0) { + curr_threads --; + upsdebugx(5, "%s: Clean-up: Joined a finished thread #%" PRIuSIZE, + __func__, i); + } else { + upsdebugx(0, "WARNING: %s: Clean-up: Accounting of thread count " + "says we are already at 0", __func__); + } + pthread_mutex_unlock(&threadcount_mutex); +# endif /* HAVE_PTHREAD_TRYJOIN */ +# endif /* HAVE_SEMAPHORE */ } - /* Prepare the next iteration */ - ip_str = nutscan_ip_ranges_iter_inc(&ip); + free(thread_array); + upsdebugx(2, "%s: all threads freed", __func__); } - } - - return nutscan_rewind_device(current_nut_dev); + pthread_mutex_destroy(&dev_mutex); + +# ifdef HAVE_SEMAPHORE + if (max_threads_scantype > 0) + sem_destroy(semaphore_scantype); +# endif /* HAVE_SEMAPHORE */ +#endif /* HAVE_PTHREAD */ + } /* end of: scan range of 1+ IP address(es), maybe in parallel */ + + result = nutscan_rewind_device(dev_ret); + dev_ret = NULL; + return result; } #else /* not WITH_IPMI */ @@ -705,4 +1076,8 @@ nutscan_device_t * nutscan_scan_ip_range_ipmi(nutscan_ip_range_list_t * irl, nu return NULL; } +int nutscan_unload_ipmi_library(void) +{ + return 0; +} #endif /* WITH_IPMI */ diff --git a/tools/nut-scanner/scan_nut.c b/tools/nut-scanner/scan_nut.c index 723c8898fb..da11aa53a5 100644 --- a/tools/nut-scanner/scan_nut.c +++ b/tools/nut-scanner/scan_nut.c @@ -30,6 +30,10 @@ #include "upsclient.h" #include "nut-scan.h" #include "nut_stdint.h" + +/* externally visible to nutscan-init */ +int nutscan_unload_upsclient_library(void); + #include #define SCAN_NUT_DRIVERNAME "dummy-ups" @@ -37,6 +41,7 @@ /* dynamic link library stuff */ static lt_dlhandle dl_handle = NULL; static const char *dl_error = NULL; +static char *dl_saved_libname = NULL; static int (*nut_upscli_splitaddr)(const char *buf, char **hostname, uint16_t *port); static int (*nut_upscli_tryconnect)(UPSCONN_t *ups, const char *host, uint16_t port, @@ -62,40 +67,52 @@ typedef int bool_t; #endif struct scan_nut_arg { + /* String includes square brackets around host/IP + * address, and/or :port suffix (if customized so): */ char * hostname; useconds_t timeout; }; -/* return 0 on error; visible externally */ +/* Return 0 on success, -1 on error e.g. "was not loaded"; + * other values may be possible if lt_dlclose() errors set them; + * visible externally */ +int nutscan_unload_library(int *avail, lt_dlhandle *pdl_handle, char **libpath); +int nutscan_unload_upsclient_library(void) +{ + return nutscan_unload_library(&nutscan_avail_nut, &dl_handle, &dl_saved_libname); +} + +/* Return 0 on error; visible externally */ int nutscan_load_upsclient_library(const char *libname_path); int nutscan_load_upsclient_library(const char *libname_path) { if (dl_handle != NULL) { - /* if previous init failed */ - if (dl_handle == (void *)1) { - return 0; - } - /* init has already been done */ - return 1; + /* if previous init failed */ + if (dl_handle == (void *)1) { + return 0; + } + /* init has already been done */ + return 1; } if (libname_path == NULL) { - fprintf(stderr, "NUT client library not found. NUT search disabled.\n"); + upsdebugx(0, "NUT client library not found. NUT search disabled."); return 0; } if (lt_dlinit() != 0) { - fprintf(stderr, "Error initializing lt_init\n"); - return 0; + upsdebugx(0, "%s: Error initializing lt_dlinit", __func__); + return 0; } dl_handle = lt_dlopen(libname_path); if (!dl_handle) { - dl_error = lt_dlerror(); - goto err; + dl_error = lt_dlerror(); + goto err; } - lt_dlerror(); /* Clear any existing error */ + /* Clear any existing error */ + lt_dlerror(); *(void **) (&nut_upscli_splitaddr) = lt_dlsym(dl_handle, "upscli_splitaddr"); @@ -127,19 +144,32 @@ int nutscan_load_upsclient_library(const char *libname_path) goto err; } + if (dl_saved_libname) + free(dl_saved_libname); + dl_saved_libname = xstrdup(libname_path); + return 1; err: - fprintf(stderr, - "Cannot load NUT library (%s) : %s. NUT search disabled.\n", + upsdebugx(0, + "Cannot load NUT library (%s) : %s. NUT search disabled.", libname_path, dl_error); dl_handle = (void *)1; lt_dlexit(); + if (dl_saved_libname) { + free(dl_saved_libname); + dl_saved_libname = NULL; + } return 0; } +/* end of dynamic link library stuff */ /* FIXME: SSL support */ -static void * list_nut_devices(void * arg) +/* Performs a (parallel-able) NUT protocol scan of one remote host:port. + * Returns NULL, updates global dev_ret when a scan is successful. + * FREES the caller's copy of "nut_arg" and "hostname" in it, if applicable. + */ +static void * list_nut_devices_thready(void * arg) { struct scan_nut_arg * nut_arg = (struct scan_nut_arg*)arg; char *target_hostname = nut_arg->hostname; @@ -147,9 +177,9 @@ static void * list_nut_devices(void * arg) uint16_t port; size_t numq, numa; const char *query[4]; - char **answer; + char **answer = NULL; char *hostname = NULL; - UPSCONN_t *ups = malloc(sizeof(*ups)); + UPSCONN_t *ups = xcalloc(1, sizeof(*ups)); nutscan_device_t * dev = NULL; size_t buf_size; @@ -162,36 +192,37 @@ static void * list_nut_devices(void * arg) upsdebugx(2, "Entering %s for %s", __func__, target_hostname); if ((*nut_upscli_splitaddr)(target_hostname, &hostname, &port) != 0) { - free(target_hostname); - free(nut_arg); - free(ups); - return NULL; + /* Avoid disconnect from not connected ups */ + if (ups) { + if (ups->host) + free(ups->host); + free(ups); + } + ups = NULL; + goto end; } if ((*nut_upscli_tryconnect)(ups, hostname, port, UPSCLI_CONN_TRYSSL, &tv) < 0) { - free(target_hostname); - free(nut_arg); - free(ups); - return NULL; + /* Avoid disconnect from not connected ups */ + if (ups) { + if (ups->host) + free(ups->host); + free(ups); + } + ups = NULL; + goto end; } if ((*nut_upscli_list_start)(ups, numq, query) < 0) { - (*nut_upscli_disconnect)(ups); - free(target_hostname); - free(nut_arg); - free(ups); - return NULL; + goto end; } while ((*nut_upscli_list_next)(ups, numq, query, &numa, &answer) == 1) { /* UPS */ if (numa < 3) { - (*nut_upscli_disconnect)(ups); - free(target_hostname); - free(nut_arg); - free(ups); - return NULL; + goto end; } + /* FIXME: check for duplication by getting driver.port and device.serial * for comparison with other busses results */ /* FIXME: @@ -252,10 +283,21 @@ static void * list_nut_devices(void * arg) } - (*nut_upscli_disconnect)(ups); - free(target_hostname); - free(nut_arg); - free(ups); +end: + if (ups) { + (*nut_upscli_disconnect)(ups); + if (ups->host) + free(ups->host); + free(ups); + } + + if (target_hostname) + free(target_hostname); + if (hostname) + free(hostname); + if (nut_arg) + free(nut_arg); + return NULL; } @@ -304,12 +346,6 @@ nutscan_device_t * nutscan_scan_ip_range_nut(nutscan_ip_range_list_t * irl, cons # endif #endif /* HAVE_PTHREAD */ -#ifdef WIN32 - WSADATA WSAdata; - WSAStartup(2,&WSAdata); - atexit((void(*)(void))WSACleanup); -#endif - #ifdef HAVE_PTHREAD pthread_mutex_init(&dev_mutex, NULL); @@ -414,7 +450,8 @@ nutscan_device_t * nutscan_scan_ip_range_nut(nutscan_ip_range_list_t * irl, cons * Otherwise, -1 is returned and errno is set, * and the state of the semaphore is unchanged. */ - int stwST = sem_trywait(semaphore_scantype), stwS = sem_trywait(semaphore); + int stwST = sem_trywait(semaphore_scantype); + int stwS = sem_trywait(semaphore); pass = ((max_threads_scantype == 0 || stwST == 0) && stwS == 0); upsdebugx(4, "%s: max_threads_scantype=%" PRIuSIZE " curr_threads=%" PRIuSIZE @@ -518,6 +555,7 @@ nutscan_device_t * nutscan_scan_ip_range_nut(nutscan_ip_range_list_t * irl, cons } if ((nut_arg = malloc(sizeof(struct scan_nut_arg))) == NULL) { + upsdebugx(0, "%s: Memory allocation error", __func__); free(ip_dest); break; } @@ -526,7 +564,7 @@ nutscan_device_t * nutscan_scan_ip_range_nut(nutscan_ip_range_list_t * irl, cons nut_arg->hostname = ip_dest; #ifdef HAVE_PTHREAD - if (pthread_create(&thread, NULL, list_nut_devices, (void*)nut_arg) == 0) { + if (pthread_create(&thread, NULL, list_nut_devices_thready, (void*)nut_arg) == 0) { nutscan_thread_t *new_thread_array; # ifdef HAVE_PTHREAD_TRYJOIN pthread_mutex_lock(&threadcount_mutex); @@ -550,11 +588,16 @@ nutscan_device_t * nutscan_scan_ip_range_nut(nutscan_ip_range_list_t * irl, cons pthread_mutex_unlock(&threadcount_mutex); # endif /* HAVE_PTHREAD_TRYJOIN */ } -#else /* not HAVE_PTHREAD */ - list_nut_devices(nut_arg); +#else /* if not HAVE_PTHREAD */ + list_nut_devices_thready(nut_arg); #endif /* if HAVE_PTHREAD */ - /* Prepare the next iteration */ + /* Prepare the next iteration; note that + * nutscan_scan_ipmi_device_thready() + * takes care of freeing "tmp_sec" and its + * copy (note strdup!) of "ip_str" as + * hostname, possibly suffixed with a port. + */ free(ip_str); ip_str = nutscan_ip_ranges_iter_inc(&ip); } else { /* if not pass -- all slots busy */ diff --git a/tools/nut-scanner/scan_nut_simulation.c b/tools/nut-scanner/scan_nut_simulation.c index b2c4cddabb..7324ddb1d8 100644 --- a/tools/nut-scanner/scan_nut_simulation.c +++ b/tools/nut-scanner/scan_nut_simulation.c @@ -48,8 +48,8 @@ nutscan_device_t * nutscan_scan_nut_simulation(void) upsdebugx(1, "Scanning: %s", CONFPATH); if ((dp = opendir(CONFPATH)) == NULL) { - upsdebugx(1, "%s: Failed to open %s: %s (%d)", - __func__, CONFPATH, strerror(errno), errno); + upsdebug_with_errno(1, "%s: Failed to open %s", + __func__, CONFPATH); upsdebugx(0, "Failed to open %s, skip NUT simulation scan", CONFPATH); return NULL; diff --git a/tools/nut-scanner/scan_snmp.c b/tools/nut-scanner/scan_snmp.c index 2a5358d1ab..13def273b6 100644 --- a/tools/nut-scanner/scan_snmp.c +++ b/tools/nut-scanner/scan_snmp.c @@ -29,6 +29,9 @@ #include "nut-scan.h" #include "nut_stdint.h" +/* externally visible to nutscan-init */ +int nutscan_unload_snmp_library(void); + #ifdef WITH_SNMP #ifndef WIN32 @@ -37,8 +40,8 @@ # undef _WIN32_WINNT #endif -#include #include +#include #include /* workaround for buggy Net-SNMP config @@ -116,7 +119,9 @@ typedef int bool_t; /* dynamic link library stuff */ static lt_dlhandle dl_handle = NULL; static const char *dl_error = NULL; +static char *dl_saved_libname = NULL; #endif /* WITH_SNMP_STATIC */ +static int nut_initialized_snmp = 0; static void (*nut_init_snmp)(const char *type); static void (*nut_snmp_sess_init)(netsnmp_session * session); @@ -177,9 +182,24 @@ static oid *nut_usmHMAC256SHA384AuthProtocol; static oid *nut_usmHMAC384SHA512AuthProtocol; #endif -/* return 0 on error; visible externally */ -int nutscan_load_snmp_library(const char *libname_path); +/* Return 0 on success, -1 on error e.g. "was not loaded"; + * other values may be possible if lt_dlclose() errors set them; + * visible externally */ +#ifndef WITH_SNMP_STATIC +int nutscan_unload_library(int *avail, lt_dlhandle *pdl_handle, char **libpath); +#endif +int nutscan_unload_snmp_library(void) +{ +#ifdef WITH_SNMP_STATIC + return 0; +#else + nut_initialized_snmp = 0; + return nutscan_unload_library(&nutscan_avail_snmp, &dl_handle, &dl_saved_libname); +#endif +} +/* Return 0 on error; visible externally */ +int nutscan_load_snmp_library(const char *libname_path); int nutscan_load_snmp_library(const char *libname_path) { #ifdef WITH_SNMP_STATIC @@ -277,12 +297,12 @@ int nutscan_load_snmp_library(const char *libname_path) } if (libname_path == NULL) { - upsdebugx(1, "SNMP library not found. SNMP search disabled"); + upsdebugx(0, "SNMP library not found. SNMP search disabled."); return 0; } if (lt_dlinit() != 0) { - upsdebugx(1, "Error initializing lt_init"); + upsdebugx(0, "%s: Error initializing lt_dlinit", __func__); return 0; } @@ -292,7 +312,9 @@ int nutscan_load_snmp_library(const char *libname_path) goto err; } - lt_dlerror(); /* Clear any existing error */ + /* Clear any existing error */ + lt_dlerror(); + *(void **) (&nut_init_snmp) = lt_dlsym(dl_handle, "init_snmp"); if ((dl_error = lt_dlerror()) != NULL) { goto err; @@ -453,17 +475,25 @@ int nutscan_load_snmp_library(const char *libname_path) } #endif /* NUT_HAVE_LIBNETSNMP_usmHMAC384SHA512AuthProtocol */ -#endif /* WITH_SNMP_STATIC */ + if (dl_saved_libname) + free(dl_saved_libname); + dl_saved_libname = xstrdup(libname_path); + +#endif /* not WITH_SNMP_STATIC */ return 1; #ifndef WITH_SNMP_STATIC err: - fprintf(stderr, - "Cannot load SNMP library (%s) : %s. SNMP search disabled.\n", + upsdebugx(0, + "Cannot load SNMP library (%s) : %s. SNMP search disabled.", libname_path, dl_error); dl_handle = (void *)1; lt_dlexit(); + if (dl_saved_libname) { + free(dl_saved_libname); + dl_saved_libname = NULL; + } return 0; #endif /* not WITH_SNMP_STATIC */ } @@ -663,15 +693,16 @@ static int init_session(struct snmp_session * snmp_sess, nutscan_snmp_t * sec) else if (strcmp(sec->secLevel, "authPriv") == 0) snmp_sess->securityLevel = SNMP_SEC_LEVEL_AUTHPRIV; else { - fprintf(stderr, - "Bad SNMPv3 securityLevel: %s\n", - sec->secLevel); + upsdebugx(0, "WARNING: %s: " + "Bad SNMPv3 securityLevel: %s", + __func__, sec->secLevel); return 0; } /* Security name */ if (sec->secName == NULL) { - fprintf(stderr, "securityName is required for SNMPv3\n"); + upsdebugx(0, "WARNING: %s: securityName is required for SNMPv3", + __func__); return 0; } snmp_sess->securityName = strdup(sec->secName); @@ -686,20 +717,20 @@ static int init_session(struct snmp_session * snmp_sess, nutscan_snmp_t * sec) switch (snmp_sess->securityLevel) { case SNMP_SEC_LEVEL_AUTHNOPRIV: if (sec->authPassword == NULL) { - fprintf(stderr, + upsdebugx(0, "WARNING: %s: " "authPassword is required " - "for SNMPv3 in %s mode\n", - sec->secLevel); + "for SNMPv3 in %s mode", + __func__, sec->secLevel); return 0; } break; case SNMP_SEC_LEVEL_AUTHPRIV: if ((sec->authPassword == NULL) || (sec->privPassword == NULL)) { - fprintf(stderr, + upsdebugx(0, "WARNING: %s: " "authPassword and privPassword are " - "required for SNMPv3 in %s mode\n", - sec->secLevel); + "required for SNMPv3 in %s mode", + __func__, sec->secLevel); return 0; } break; @@ -761,9 +792,9 @@ static int init_session(struct snmp_session * snmp_sess, nutscan_snmp_t * sec) #else { #endif - fprintf(stderr, - "Bad SNMPv3 authProtocol: %s\n", - sec->authProtocol); + upsdebugx(0, "WARNING: %s: " + "Bad SNMPv3 authProtocol: %s", + __func__, sec->authProtocol); return 0; } } @@ -783,8 +814,8 @@ static int init_session(struct snmp_session * snmp_sess, nutscan_snmp_t * sec) #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) ) # pragma GCC diagnostic pop #endif - fprintf(stderr, "Bad SNMPv3 securityAuthProtoLen: %" PRIuSIZE, - snmp_sess->securityAuthProtoLen); + upsdebugx(0, "WARNING: %s: Bad SNMPv3 securityAuthProtoLen: %" PRIuSIZE, + __func__, snmp_sess->securityAuthProtoLen); return 0; } if ((*nut_generate_Ku)(snmp_sess->securityAuthProto, @@ -795,9 +826,10 @@ static int init_session(struct snmp_session * snmp_sess, nutscan_snmp_t * sec) &snmp_sess->securityAuthKeyLen) != SNMPERR_SUCCESS ) { - fprintf(stderr, + upsdebugx(0, "WARNING: %s: " "Error generating Ku from " - "authentication pass phrase\n"); + "authentication pass phrase", + __func__); return 0; } @@ -848,9 +880,9 @@ static int init_session(struct snmp_session * snmp_sess, nutscan_snmp_t * sec) #else { #endif - fprintf(stderr, - "Bad SNMPv3 privProtocol: %s\n", - sec->privProtocol); + upsdebugx(0, "WARNING: %s: " + "Bad SNMPv3 privProtocol: %s", + __func__, sec->privProtocol); return 0; } } @@ -871,8 +903,8 @@ static int init_session(struct snmp_session * snmp_sess, nutscan_snmp_t * sec) #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) ) # pragma GCC diagnostic pop #endif - fprintf(stderr, "Bad SNMPv3 securityAuthProtoLen: %" PRIuSIZE, - snmp_sess->securityAuthProtoLen); + upsdebugx(0, "WARNING: %s: Bad SNMPv3 securityAuthProtoLen: %" PRIuSIZE, + __func__, snmp_sess->securityAuthProtoLen); return 0; } if ((*nut_generate_Ku)(snmp_sess->securityAuthProto, @@ -883,9 +915,10 @@ static int init_session(struct snmp_session * snmp_sess, nutscan_snmp_t * sec) &snmp_sess->securityPrivKeyLen) != SNMPERR_SUCCESS ) { - fprintf(stderr, + upsdebugx(0, "WARNING: %s: " "Error generating Ku from " - "private pass phrase\n"); + "private pass phrase", + __func__); return 0; } @@ -894,7 +927,17 @@ static int init_session(struct snmp_session * snmp_sess, nutscan_snmp_t * sec) return 1; } -static void * try_SysOID(void * arg) +static void * wrap_nut_snmp_sess_open(struct snmp_session *session) +{ + /* Open the session */ + return (*nut_snmp_sess_open)(session); /* establish the session */ +} + +/* Performs a (parallel-able) SNMP protocol scan of one remote host. + * Returns NULL, updates global dev_ret when a scan is successful. + * FREES the caller's copy of "sec" and "peername" in it, if applicable. + */ +static void * try_SysOID_thready(void * arg) { struct snmp_session snmp_sess; void * handle; @@ -919,7 +962,7 @@ static void * try_SysOID(void * arg) snmp_sess.timeout = (long)g_usec_timeout; /* Open the session */ - handle = (*nut_snmp_sess_open)(&snmp_sess); /* establish the session */ + handle = wrap_nut_snmp_sess_open(&snmp_sess); /* establish the session */ if (handle == NULL) { upsdebugx(2, "Failed to open SNMP session for %s", @@ -940,7 +983,7 @@ static void * try_SysOID(void * arg) pdu = (*nut_snmp_pdu_create)(SNMP_MSG_GET); if (pdu == NULL) { - fprintf(stderr, "Not enough memory\n"); + upsdebugx(0, "%s: Memory allocation error", __func__); (*nut_snmp_sess_close)(handle); goto try_SysOID_free; } @@ -1025,6 +1068,15 @@ static void * try_SysOID(void * arg) return NULL; } +static void init_snmp_once(void) +{ + /* Initialize the SNMP library */ + if (!nut_initialized_snmp) { + (*nut_init_snmp)("nut-scanner"); + nut_initialized_snmp = 1; + } +} + nutscan_device_t * nutscan_scan_snmp(const char * start_ip, const char * stop_ip, useconds_t usec_timeout, nutscan_snmp_t * sec) { @@ -1067,12 +1119,6 @@ nutscan_device_t * nutscan_scan_ip_range_snmp(nutscan_ip_range_list_t * irl, # endif #endif /* HAVE_PTHREAD */ -#ifdef WIN32 - WSADATA WSAdata; - WSAStartup(2,&WSAdata); - atexit((void(*)(void))WSACleanup); -#endif - #ifdef HAVE_PTHREAD pthread_mutex_init(&dev_mutex, NULL); @@ -1134,14 +1180,13 @@ nutscan_device_t * nutscan_scan_ip_range_snmp(nutscan_ip_range_list_t * irl, g_usec_timeout = usec_timeout; - /* Force numeric OIDs resolution (ie, do not resolve to textual names) + /* Force numeric OIDs resolution (i.e., do not resolve to textual names) * This is mostly for the convenience of debug output */ if (nut_snmp_out_toggle_options("n") != NULL) { upsdebugx(1, "Failed to enable numeric OIDs resolution"); } - /* Initialize the SNMP library */ - (*nut_init_snmp)("nut-scanner"); + init_snmp_once(); ip_str = nutscan_ip_ranges_iter_init(&ip, irl); @@ -1171,7 +1216,8 @@ nutscan_device_t * nutscan_scan_ip_range_snmp(nutscan_ip_range_list_t * irl, * Otherwise, -1 is returned and errno is set, * and the state of the semaphore is unchanged. */ - int stwST = sem_trywait(semaphore_scantype), stwS = sem_trywait(semaphore); + int stwST = sem_trywait(semaphore_scantype); + int stwS = sem_trywait(semaphore); pass = ((max_threads_scantype == 0 || stwST == 0) && stwS == 0); upsdebugx(4, "%s: max_threads_scantype=%" PRIuSIZE " curr_threads=%" PRIuSIZE @@ -1261,11 +1307,16 @@ nutscan_device_t * nutscan_scan_ip_range_snmp(nutscan_ip_range_list_t * irl, if (pass) { tmp_sec = malloc(sizeof(nutscan_snmp_t)); + if (tmp_sec == NULL) { + upsdebugx(0, "%s: Memory allocation error", __func__); + break; + } + memcpy(tmp_sec, sec, sizeof(nutscan_snmp_t)); tmp_sec->peername = ip_str; #ifdef HAVE_PTHREAD - if (pthread_create(&thread, NULL, try_SysOID, (void*)tmp_sec) == 0) { + if (pthread_create(&thread, NULL, try_SysOID_thready, (void*)tmp_sec) == 0) { nutscan_thread_t *new_thread_array; # ifdef HAVE_PTHREAD_TRYJOIN pthread_mutex_lock(&threadcount_mutex); @@ -1289,14 +1340,17 @@ nutscan_device_t * nutscan_scan_ip_range_snmp(nutscan_ip_range_list_t * irl, pthread_mutex_unlock(&threadcount_mutex); # endif /* HAVE_PTHREAD_TRYJOIN */ } -#else /* not HAVE_PTHREAD */ - try_SysOID((void *)tmp_sec); +#else /* if not HAVE_PTHREAD */ + try_SysOID_thready(tmp_sec); #endif /* if HAVE_PTHREAD */ - /* Prepare the next iteration */ -/* free(ip_str); */ /* Do not free() here - seems to cause a double-free instead */ + /* Prepare the next iteration; note that + * try_SysOID_thready() + * takes care of freeing "tmp_sec" and its + * reference (NOT strdup!) to "ip_str" as + * peername. + */ ip_str = nutscan_ip_ranges_iter_inc(&ip); -/* free(tmp_sec); */ } else { /* if not pass -- all slots busy */ #ifdef HAVE_PTHREAD # ifdef HAVE_SEMAPHORE @@ -1415,4 +1469,8 @@ nutscan_device_t * nutscan_scan_ip_range_snmp(nutscan_ip_range_list_t * irl, return NULL; } +int nutscan_unload_snmp_library(void) +{ + return 0; +} #endif /* WITH_SNMP */ diff --git a/tools/nut-scanner/scan_usb.c b/tools/nut-scanner/scan_usb.c index 164f6c7983..67d20b578c 100644 --- a/tools/nut-scanner/scan_usb.c +++ b/tools/nut-scanner/scan_usb.c @@ -26,6 +26,9 @@ #include "common.h" #include "nut-scan.h" +/* externally visible to nutscan-init */ +int nutscan_unload_usb_library(void); + #ifdef WITH_USB #include "upsclient.h" @@ -37,6 +40,8 @@ /* dynamic link library stuff */ static lt_dlhandle dl_handle = NULL; static const char *dl_error = NULL; +static char *dl_saved_libname = NULL; + static int (*nut_usb_close)(libusb_device_handle *dev); static int (*nut_usb_get_string_simple)(libusb_device_handle *dev, int index, char *buf, size_t buflen); @@ -76,7 +81,16 @@ static int (*nut_usb_get_string_simple)(libusb_device_handle *dev, int index, static char * (*nut_usb_strerror)(void); #endif /* WITH_LIBUSB_1_0 */ -/* return 0 on error; visible externally */ +/* Return 0 on success, -1 on error e.g. "was not loaded"; + * other values may be possible if lt_dlclose() errors set them; + * visible externally */ +int nutscan_unload_library(int *avail, lt_dlhandle *pdl_handle, char **libpath); +int nutscan_unload_usb_library(void) +{ + return nutscan_unload_library(&nutscan_avail_usb, &dl_handle, &dl_saved_libname); +} + +/* Return 0 on error; visible externally */ int nutscan_load_usb_library(const char *libname_path); int nutscan_load_usb_library(const char *libname_path) { @@ -90,12 +104,12 @@ int nutscan_load_usb_library(const char *libname_path) } if (libname_path == NULL) { - fprintf(stderr, "USB library not found. USB search disabled.\n"); + upsdebugx(0, "USB library not found. USB search disabled."); return 0; } if (lt_dlinit() != 0) { - fprintf(stderr, "Error initializing lt_init\n"); + upsdebugx(0, "%s: Error initializing lt_dlinit", __func__); return 0; } @@ -104,7 +118,9 @@ int nutscan_load_usb_library(const char *libname_path) dl_error = lt_dlerror(); goto err; } - lt_dlerror(); /* Clear any existing error */ + + /* Clear any existing error */ + lt_dlerror(); *(void **) (&nut_usb_init) = lt_dlsym(dl_handle, USB_INIT_SYMBOL); if ((dl_error = lt_dlerror()) != NULL) { @@ -167,10 +183,10 @@ int nutscan_load_usb_library(const char *libname_path) *(void **) (&nut_usb_get_port_number) = lt_dlsym(dl_handle, "libusb_get_port_number"); if ((dl_error = lt_dlerror()) != NULL) { - fprintf(stderr, + upsdebugx(0, "WARNING: %s: " "While loading USB library (%s), failed to find libusb_get_port_number() : %s. " - "The \"busport\" USB matching option will be disabled.\n", - libname_path, dl_error); + "The \"busport\" USB matching option will be disabled.", + __func__, libname_path, dl_error); nut_usb_get_port_number = NULL; } @@ -219,14 +235,22 @@ int nutscan_load_usb_library(const char *libname_path) } #endif /* WITH_LIBUSB_1_0 */ + if (dl_saved_libname) + free(dl_saved_libname); + dl_saved_libname = xstrdup(libname_path); + return 1; err: - fprintf(stderr, - "Cannot load USB library (%s) : %s. USB search disabled.\n", + upsdebugx(0, + "Cannot load USB library (%s) : %s. USB search disabled.", libname_path, dl_error); dl_handle = (void *)1; lt_dlexit(); + if (dl_saved_libname) { + free(dl_saved_libname); + dl_saved_libname = NULL; + } return 0; } /* end of dynamic link library stuff */ @@ -424,9 +448,12 @@ nutscan_device_t * nutscan_scan_usb(nutscan_usb_t * scanopts) #if WITH_LIBUSB_1_0 ret = (*nut_usb_open)(dev, &udev); if (!udev || ret != LIBUSB_SUCCESS) { - fprintf(stderr, "Failed to open device " - "bus '%s' device/port '%s' bus/port '%s', skipping: %s\n", - busname, device_port, bus_port, + upsdebugx(0, "WARNING: %s: " + "Failed to open device " + "bus '%s' device/port '%s' " + "bus/port '%s', skipping: %s", + __func__, busname, + device_port, bus_port, (*nut_usb_strerror)(ret)); /* Note: closing is not applicable @@ -448,9 +475,10 @@ nutscan_device_t * nutscan_scan_usb(nutscan_usb_t * scanopts) udev = (*nut_usb_open)(dev); if (!udev) { /* TOTHINK: any errno or similar to test? */ - fprintf(stderr, "Failed to open device " - "bus '%s' device/port '%s', skipping: %s\n", - busname, device_port, + upsdebugx(0, "WARNING: %s: " + "Failed to open device " + "bus '%s' device/port '%s', skipping: %s", + __func__, busname, device_port, (*nut_usb_strerror)()); continue; } @@ -530,8 +558,7 @@ nutscan_device_t * nutscan_scan_usb(nutscan_usb_t * scanopts) nut_dev = nutscan_new_device(); if (nut_dev == NULL) { - fprintf(stderr, - "Memory allocation error\n"); + upsdebugx(0, "%s: Memory allocation error", __func__); nutscan_free_device(current_nut_dev); free(serialnumber); free(device_name); @@ -663,4 +690,9 @@ nutscan_device_t * nutscan_scan_usb(nutscan_usb_t * scanopts) return NULL; } + +int nutscan_unload_usb_library(void) +{ + return 0; +} #endif /* WITH_USB */ diff --git a/tools/nut-scanner/scan_xml_http.c b/tools/nut-scanner/scan_xml_http.c index db94192a26..9bec46f37b 100644 --- a/tools/nut-scanner/scan_xml_http.c +++ b/tools/nut-scanner/scan_xml_http.c @@ -30,6 +30,9 @@ #include "nut-scan.h" #include "nut_stdint.h" +/* externally visible to nutscan-init */ +int nutscan_unload_neon_library(void); + #ifdef WITH_NEON #ifndef WIN32 @@ -53,13 +56,13 @@ #include #include -#include #include #include /* dynamic link library stuff */ static lt_dlhandle dl_handle = NULL; static const char *dl_error = NULL; +static char *dl_saved_libname = NULL; static void (*nut_ne_xml_push_handler)(ne_xml_parser *p, ne_xml_startelm_cb *startelm, @@ -85,7 +88,16 @@ typedef enum ebool { FALSE = 0, TRUE } bool_t; typedef int bool_t; #endif -/* return 0 on error; visible externally */ +/* Return 0 on success, -1 on error e.g. "was not loaded"; + * other values may be possible if lt_dlclose() errors set them; + * visible externally */ +int nutscan_unload_library(int *avail, lt_dlhandle *pdl_handle, char **libpath); +int nutscan_unload_neon_library(void) +{ + return nutscan_unload_library(&nutscan_avail_xml_http, &dl_handle, &dl_saved_libname); +} + +/* Return 0 on error; visible externally */ int nutscan_load_neon_library(const char *libname_path); int nutscan_load_neon_library(const char *libname_path) { @@ -99,12 +111,12 @@ int nutscan_load_neon_library(const char *libname_path) } if (libname_path == NULL) { - fprintf(stderr, "Neon library not found. XML search disabled.\n"); + upsdebugx(0, "Neon library not found. XML search disabled."); return 0; } if (lt_dlinit() != 0) { - fprintf(stderr, "Error initializing lt_init\n"); + upsdebugx(0, "%s: Error initializing lt_dlinit", __func__); return 0; } @@ -114,7 +126,9 @@ int nutscan_load_neon_library(const char *libname_path) goto err; } - lt_dlerror(); /* Clear any existing error */ + /* Clear any existing error */ + lt_dlerror(); + *(void **) (&nut_ne_xml_push_handler) = lt_dlsym(dl_handle, "ne_xml_push_handler"); if ((dl_error = lt_dlerror()) != NULL) { @@ -141,15 +155,25 @@ int nutscan_load_neon_library(const char *libname_path) goto err; } + if (dl_saved_libname) + free(dl_saved_libname); + dl_saved_libname = xstrdup(libname_path); + return 1; + err: - fprintf(stderr, - "Cannot load XML library (%s) : %s. XML search disabled.\n", + upsdebugx(0, + "Cannot load XML library (%s) : %s. XML search disabled.", libname_path, dl_error); dl_handle = (void *)1; lt_dlexit(); + if (dl_saved_libname) { + free(dl_saved_libname); + dl_saved_libname = NULL; + } return 0; } +/* end of dynamic link library stuff */ /* A start-element callback for element with given namespace/name. */ static int startelm_cb(void *userdata, int parent, const char *nspace, const char *name, const char **atts) { @@ -182,17 +206,23 @@ static int startelm_cb(void *userdata, int parent, const char *nspace, const cha return result; } -static void * nutscan_scan_xml_http_generic(void * arg) +/* Performs a (parallel-able) NetXML protocol scan of one remote host:port. + * Returns NULL, updates global dev_ret when a scan is successful. + * FREES the caller's copy of "arg" and "hostname" in it, if applicable. + */ +static void * nutscan_scan_xml_http_thready(void * arg) { nutscan_xml_t * sec = (nutscan_xml_t *)arg; char *scanMsg = ""; -/* Note: at this time the HTTP/XML scan is in fact not implemented - just the UDP part */ + /* Note: at this time the HTTP/XML scan is + * in fact not implemented - just the UDP part */ /* uint16_t port_http = 80; */ uint16_t port_udp = 4679; -/* A NULL "ip" causes a broadcast scan; otherwise the single ip address is queried directly */ + /* A NULL "ip" causes a broadcast scan; otherwise + * the single ip address is queried directly */ char *ip = NULL; useconds_t usec_timeout = 0; - int peerSocket; + int peerSocket = -1; int sockopt_on = 1; struct sockaddr_in sockAddress_udp; socklen_t sockAddressLength = sizeof(sockAddress_udp); @@ -221,34 +251,36 @@ static void * nutscan_scan_xml_http_generic(void * arg) usec_timeout = 5000000; /* Driver default : 5sec */ if (!nutscan_avail_xml_http) { - return NULL; + goto end_free; } if ((peerSocket = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { - fprintf(stderr, "Error creating socket\n"); - return NULL; + upsdebugx(0, "%s: Error creating socket for %s", + __func__, ip ? ip : "broadcast"); + goto end_free; } -/* FIXME : Per http://stackoverflow.com/questions/683624/udp-broadcast-on-all-interfaces - * A single sendto() generates a single packet, so one must iterate all known interfaces... */ + /* FIXME : Per http://stackoverflow.com/questions/683624/udp-broadcast-on-all-interfaces + * A single sendto() generates a single packet, + * so one must iterate all known interfaces... */ #define MAX_RETRIES 3 for (i = 0; i != MAX_RETRIES ; i++) { /* Initialize socket */ sockAddress_udp.sin_family = AF_INET; if (ip == NULL) { upsdebugx(2, - "nutscan_scan_xml_http_generic() : scanning connected network segment(s) " + "%s: scanning connected network segment(s) " "with a broadcast, attempt %d of %d with a timeout of %" PRIdMAX " usec", - (i + 1), MAX_RETRIES, (uintmax_t)usec_timeout); + __func__, (i + 1), MAX_RETRIES, (uintmax_t)usec_timeout); sockAddress_udp.sin_addr.s_addr = INADDR_BROADCAST; setsockopt(peerSocket, SOL_SOCKET, SO_BROADCAST, SOCK_OPT_CAST &sockopt_on, sizeof(sockopt_on)); } else { upsdebugx(2, - "nutscan_scan_xml_http_generic() : scanning IP '%s' with a unicast, " + "%s: scanning IP '%s' with a unicast, " "attempt %d of %d with a timeout of %" PRIdMAX " usec", - ip, (i + 1), MAX_RETRIES, (uintmax_t)usec_timeout); + __func__, ip, (i + 1), MAX_RETRIES, (uintmax_t)usec_timeout); inet_pton(AF_INET, ip, &(sockAddress_udp.sin_addr)); } sockAddress_udp.sin_port = htons(port_udp); @@ -258,8 +290,9 @@ static void * nutscan_scan_xml_http_generic(void * arg) (struct sockaddr *)&sockAddress_udp, sockAddressLength) <= 0 ) { - fprintf(stderr, - "Error sending Eaton to %s, #%d/%d\n", + upsdebugx(0, "%s: " + "Error sending Eaton to %s, #%d/%d", + __func__, (ip ? ip : ""), (i + 1), MAX_RETRIES); usleep(usec_timeout); continue; @@ -273,9 +306,9 @@ static void * nutscan_scan_xml_http_generic(void * arg) timeout.tv_sec = usec_timeout / 1000000; timeout.tv_usec = usec_timeout % 1000000; - upsdebugx(5, "nutscan_scan_xml_http_generic() : sent request to %s, " + upsdebugx(5, "%s: sent request to %s, " "loop #%d/%d, waiting for responses", - (ip ? ip : ""), (i + 1), MAX_RETRIES); + __func__, (ip ? ip : ""), (i + 1), MAX_RETRIES); while ((ret = select(peerSocket + 1, &fds, NULL, NULL, &timeout)) ) { @@ -283,17 +316,17 @@ static void * nutscan_scan_xml_http_generic(void * arg) int parserFailed; retNum ++; - upsdebugx(5, "nutscan_scan_xml_http_generic() : request to %s, " + upsdebugx(5, "%s: request to %s, " "loop #%d/%d, response #%d", - (ip ? ip : ""), (i + 1), MAX_RETRIES, retNum); + __func__, (ip ? ip : ""), (i + 1), MAX_RETRIES, retNum); timeout.tv_sec = usec_timeout / 1000000; timeout.tv_usec = usec_timeout % 1000000; if (ret == -1) { - fprintf(stderr, - "Error waiting on \ - socket: %d\n", errno); + upsdebug_with_errno(0, + "%s: Error waiting on socket", + __func__); break; } @@ -304,9 +337,9 @@ static void * nutscan_scan_xml_http_generic(void * arg) &sockAddressLength); if (recv_size < 0) { - fprintf(stderr, - "Error reading \ - socket: %d, #%d/%d\n", errno, (i + 1), MAX_RETRIES); + upsdebug_with_errno(0, "%s: " + "Error reading socket: #%d/%d", + __func__, (i + 1), MAX_RETRIES); usleep(usec_timeout); continue; } @@ -317,15 +350,15 @@ static void * nutscan_scan_xml_http_generic(void * arg) sizeof(string), NULL, 0, NI_NUMERICHOST) != 0 ) { - fprintf(stderr, - "Error converting IP address: %d\n", errno); + upsdebug_with_errno(0, "%s: " + "Error converting IP address", __func__); usleep(usec_timeout); continue; } nut_dev = nutscan_new_device(); if (nut_dev == NULL) { - fprintf(stderr, "Memory allocation error\n"); + upsdebugx(0, "%s: Memory allocation error", __func__); goto end_abort; } @@ -333,9 +366,9 @@ static void * nutscan_scan_xml_http_generic(void * arg) pthread_mutex_lock(&dev_mutex); #endif upsdebugx(5, - "Some host at IP %s replied to NetXML UDP request on port %d, " + "%s: Some host at IP %s replied to NetXML UDP request on port %d, " "inspecting the response...", - string, port_udp); + __func__, string, port_udp); nut_dev->type = TYPE_XML; /* Try to read device type */ parser = (*nut_ne_xml_create)(); @@ -353,9 +386,8 @@ static void * nutscan_scan_xml_http_generic(void * arg) * Does our driver support the notation? */ nut_dev->port = strdup(buf); upsdebugx(3, - "nutscan_scan_xml_http_generic(): " - "Adding configuration for driver='%s' port='%s'", - nut_dev->driver, nut_dev->port); + "%s: Adding configuration for driver='%s' port='%s'", + __func__, nut_dev->driver, nut_dev->port); dev_ret = nutscan_add_device_to_device( dev_ret, nut_dev); #ifdef HAVE_PTHREAD @@ -364,10 +396,10 @@ static void * nutscan_scan_xml_http_generic(void * arg) } else { - fprintf(stderr, + upsdebugx(0, "WARNING: %s: " "Device at IP %s replied with NetXML but was not deemed compatible " - "with 'netxml-ups' driver (unsupported protocol version, etc.)\n", - string); + "with 'netxml-ups' driver (unsupported protocol version, etc.)", + __func__, string); nutscan_free_device(nut_dev); nut_dev = NULL; #ifdef HAVE_PTHREAD @@ -383,32 +415,44 @@ static void * nutscan_scan_xml_http_generic(void * arg) if (ip != NULL) { upsdebugx(2, - "nutscan_scan_xml_http_generic(): we collected one reply " + "%s: we collected one reply " "to unicast for %s (repsponse from %s), done", - ip, string); + __func__, ip, string); goto end; } } /* while select() responses */ if (ip == NULL && dev_ret != NULL) { upsdebugx(2, - "nutscan_scan_xml_http_generic(): we collected one round of replies " - "to broadcast with no errors, done"); + "%s: we collected one round of replies " + "to broadcast with no errors, done", __func__); goto end; } } } upsdebugx(2, - "nutscan_scan_xml_http_generic(): no replies collected for %s, done", - (ip ? ip : "")); + "%s: no replies collected for %s, done", + __func__, (ip ? ip : "")); goto end; end_abort: upsdebugx(1, - "Had to abort nutscan_scan_xml_http_generic() for %s, see fatal details above", - (ip ? ip : "")); + "%s: Had to abort scan for %s, see fatal details above", + __func__, (ip ? ip : "")); + end: - if (ip != NULL) /* do not free "ip", it comes from caller */ + /* Broadcast is also a socket! */ + if (peerSocket != -1) close(peerSocket); + +end_free: + /* free resources which come from the caller + * (in parallel runs, nobody else can reap them) + */ + if (ip != NULL) + free(ip); + if (sec != NULL) + free(sec); + return NULL; } @@ -440,8 +484,8 @@ nutscan_device_t * nutscan_scan_xml_http_range(const char * start_ip, const char nutscan_device_t * nutscan_scan_ip_range_xml_http(nutscan_ip_range_list_t * irl, useconds_t usec_timeout, nutscan_xml_t * sec) { bool_t pass = TRUE; /* Track that we may spawn a scanning thread */ - nutscan_xml_t * tmp_sec = NULL; nutscan_device_t * result = NULL; + nutscan_xml_t * tmp_sec = NULL; if (!nutscan_avail_xml_http) { return NULL; @@ -459,6 +503,7 @@ nutscan_device_t * nutscan_scan_ip_range_xml_http(nutscan_ip_range_list_t * irl, /* Iterate the one or a range of IPs to scan */ nutscan_ip_range_list_iter_t ip; char * ip_str = NULL; + #ifdef HAVE_PTHREAD # ifdef HAVE_SEMAPHORE sem_t * semaphore = nutscan_semaphore(); @@ -550,7 +595,8 @@ nutscan_device_t * nutscan_scan_ip_range_xml_http(nutscan_ip_range_list_t * irl, * Otherwise, -1 is returned and errno is set, * and the state of the semaphore is unchanged. */ - int stwST = sem_trywait(semaphore_scantype), stwS = sem_trywait(semaphore); + int stwST = sem_trywait(semaphore_scantype); + int stwS = sem_trywait(semaphore); pass = ((max_threads_scantype == 0 || stwST == 0) && stwS == 0); upsdebugx(4, "%s: max_threads_scantype=%" PRIuSIZE " curr_threads=%" PRIuSIZE @@ -641,10 +687,10 @@ nutscan_device_t * nutscan_scan_ip_range_xml_http(nutscan_ip_range_list_t * irl, if (pass) { tmp_sec = malloc(sizeof(nutscan_xml_t)); if (tmp_sec == NULL) { - fprintf(stderr, - "Memory allocation error\n"); - return NULL; + upsdebugx(0, "%s: Memory allocation error", __func__); + break; } + memcpy(tmp_sec, sec, sizeof(nutscan_xml_t)); tmp_sec->peername = ip_str; if (tmp_sec->usec_timeout <= 0) { @@ -652,7 +698,7 @@ nutscan_device_t * nutscan_scan_ip_range_xml_http(nutscan_ip_range_list_t * irl, } #ifdef HAVE_PTHREAD - if (pthread_create(&thread, NULL, nutscan_scan_xml_http_generic, (void *)tmp_sec) == 0) { + if (pthread_create(&thread, NULL, nutscan_scan_xml_http_thready, (void*)tmp_sec) == 0) { nutscan_thread_t *new_thread_array; # ifdef HAVE_PTHREAD_TRYJOIN pthread_mutex_lock(&threadcount_mutex); @@ -676,14 +722,17 @@ nutscan_device_t * nutscan_scan_ip_range_xml_http(nutscan_ip_range_list_t * irl, pthread_mutex_unlock(&threadcount_mutex); # endif /* HAVE_PTHREAD_TRYJOIN */ } -#else /* not HAVE_PTHREAD */ - nutscan_scan_xml_http_generic((void *)tmp_sec); +#else /* if not HAVE_PTHREAD */ + nutscan_scan_xml_http_thready(tmp_sec); #endif /* if HAVE_PTHREAD */ - /* Prepare the next iteration */ -/* free(ip_str); */ /* One of these free()s seems to cause a double-free instead */ + /* Prepare the next iteration; note that + * nutscan_scan_xml_http_thready() + * takes care of freeing "tmp_sec" and its + * reference (NOT strdup!) to "ip_str" as + * peername. + */ ip_str = nutscan_ip_ranges_iter_inc(&ip); -/* free(tmp_sec); */ } else { /* if not pass -- all slots busy */ #ifdef HAVE_PTHREAD # ifdef HAVE_SEMAPHORE @@ -780,8 +829,7 @@ nutscan_device_t * nutscan_scan_ip_range_xml_http(nutscan_ip_range_list_t * irl, /* both start_ip == end_ip == NULL, scan broadcast */ tmp_sec = malloc(sizeof(nutscan_xml_t)); if (tmp_sec == NULL) { - fprintf(stderr, - "Memory allocation error\n"); + upsdebugx(0, "%s: Memory allocation error", __func__); return NULL; } @@ -797,10 +845,10 @@ nutscan_device_t * nutscan_scan_ip_range_xml_http(nutscan_ip_range_list_t * irl, tmp_sec->usec_timeout = usec_timeout; } - nutscan_scan_xml_http_generic(tmp_sec); + /* Note: the thready method releases the resources */ + nutscan_scan_xml_http_thready(tmp_sec); result = nutscan_rewind_device(dev_ret); dev_ret = NULL; - free(tmp_sec); return result; } @@ -827,4 +875,8 @@ nutscan_device_t * nutscan_scan_ip_range_xml_http(nutscan_ip_range_list_t * irl, return NULL; } +int nutscan_unload_neon_library(void) +{ + return 0; +} #endif /* WITH_NEON */