diff --git a/clients/Makefile.am b/clients/Makefile.am index aba351a3f7..35e78a8cbb 100644 --- a/clients/Makefile.am +++ b/clients/Makefile.am @@ -1,6 +1,11 @@ # Network UPS Tools: clients EXTRA_DIST = +# nutclient.cpp for some legacy reason (maybe initial detached development?) +# optionally includes "common.h" with the NUT build setup - and this option +# was never triggered in fact, not until pushed through command line like this: +AM_CXXFLAGS = -DHAVE_NUTCOMMON=1 -I$(top_srcdir)/include + # by default, link programs in this directory with libcommon.a LDADD = ../common/libcommon.la libupsclient.la $(NETLIBS) if WITH_SSL @@ -67,6 +72,9 @@ if HAVE_CXX11 # libnutclient version information and build libnutclient_la_SOURCES = nutclient.h nutclient.cpp libnutclient_la_LDFLAGS = -version-info 1:0:0 +# Needed in not-standalone builds with -DHAVE_NUTCOMMON=1 +# which is defined for in-tree CXX builds above: +libnutclient_la_LIBADD = $(top_builddir)/common/libcommonclient.la else EXTRA_DIST += nutclient.h nutclient.cpp endif diff --git a/clients/nutclient.cpp b/clients/nutclient.cpp index d0170c3919..cc5cbd801b 100644 --- a/clients/nutclient.cpp +++ b/clients/nutclient.cpp @@ -60,6 +60,23 @@ static inline void *xrealloc(void *ptr, size_t size){return realloc(ptr, size);} static inline char *xstrdup(const char *string){return strdup(string);} #endif /* HAVE_NUTCOMMON */ +/* To stay in line with modern C++, we use nullptr (not numeric NULL + * or shim __null on some systems) which was defined after C++98. + * The NUT C++ interface is intended for C++11 and newer, so we + * quiesce these warnigns if possible. + * An idea might be to detect if we do build with old C++ standard versions + * and define a nullptr like https://stackoverflow.com/a/44517878/4715872 + * but again - currently we do not intend to support that officially. + */ +#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_CXX98_COMPAT +#pragma GCC diagnostic push +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_CXX98_COMPAT_PEDANTIC +#pragma GCC diagnostic ignored "-Wc++98-compat-pedantic" +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_CXX98_COMPAT +#pragma GCC diagnostic ignored "-Wc++98-compat" +#endif namespace nut { @@ -81,6 +98,14 @@ std::string SystemException::err() } } +/* Implemented out-of-line to avoid "Weak vtables" warnings and related overheads */ +NutException::~NutException() {} +SystemException::~SystemException() {} +IOException::~IOException() {} +UnknownHostException::~UnknownHostException() {} +NotConnectedException::~NotConnectedException() {} +TimeoutException::~TimeoutException() {} + namespace internal { @@ -152,7 +177,7 @@ void Socket::connect(const std::string& host, int port) throw nut::UnknownHostException(); } - snprintf(sport, sizeof(sport), "%hu", (unsigned short int)port); + snprintf(sport, sizeof(sport), "%hu", static_cast(port)); memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; @@ -175,7 +200,7 @@ void Socket::connect(const std::string& host, int port) } } - for (ai = res; ai != NULL; ai = ai->ai_next) { + for (ai = res; ai != nullptr; ai = ai->ai_next) { sock_fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); @@ -202,11 +227,11 @@ void Socket::connect(const std::string& host, int port) if(errno == EINPROGRESS) { FD_ZERO(&wfds); FD_SET(sock_fd, &wfds); - select(sock_fd+1,NULL,&wfds,NULL, hasTimeout()?&_tv:NULL); + select(sock_fd+1, nullptr, &wfds, nullptr, hasTimeout() ? &_tv : nullptr); if (FD_ISSET(sock_fd, &wfds)) { error_size = sizeof(error); - getsockopt(sock_fd,SOL_SOCKET,SO_ERROR, - &error,&error_size); + getsockopt(sock_fd, SOL_SOCKET, SO_ERROR, + &error, &error_size); if( error == 0) { /* connect successful */ v = 0; @@ -262,10 +287,10 @@ void Socket::connect(const std::string& host, int port) #ifdef OLD - struct hostent *hostinfo = NULL; + struct hostent *hostinfo = nullptr; SOCKADDR_IN sin = { 0 }; hostinfo = ::gethostbyname(host.c_str()); - if(hostinfo == NULL) /* Host doesnt exist */ + if(hostinfo == nullptr) /* Host doesnt exist */ { throw nut::UnknownHostException(); } @@ -316,7 +341,7 @@ size_t Socket::read(void* buf, size_t sz) fd_set fds; FD_ZERO(&fds); FD_SET(_sock, &fds); - int ret = select(_sock+1, &fds, NULL, NULL, &_tv); + int ret = select(_sock+1, &fds, nullptr, nullptr, &_tv); if (ret < 1) { throw nut::TimeoutException(); } @@ -328,7 +353,7 @@ size_t Socket::read(void* buf, size_t sz) disconnect(); throw nut::IOException("Error while reading on socket"); } - return (size_t) res; + return static_cast(res); } size_t Socket::write(const void* buf, size_t sz) @@ -343,7 +368,7 @@ size_t Socket::write(const void* buf, size_t sz) fd_set fds; FD_ZERO(&fds); FD_SET(_sock, &fds); - int ret = select(_sock+1, NULL, &fds, NULL, &_tv); + int ret = select(_sock+1, nullptr, &fds, nullptr, &_tv); if (ret < 1) { throw nut::TimeoutException(); } @@ -355,7 +380,7 @@ size_t Socket::write(const void* buf, size_t sz) disconnect(); throw nut::IOException("Error while writing on socket"); } - return (size_t) res; + return static_cast(res); } std::string Socket::read() @@ -406,7 +431,23 @@ void Socket::write(const std::string& str) * */ +/* Pedantic builds complain about the static variable below... + * It is assumed safe to ignore since it is a std::string with + * no complex teardown at program exit. + */ +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_EXIT_TIME_DESTRUCTORS || defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_GLOBAL_CONSTRUCTORS) +#pragma GCC diagnostic push +# ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_GLOBAL_CONSTRUCTORS +# pragma GCC diagnostic ignored "-Wglobal-constructors" +# endif +# ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_EXIT_TIME_DESTRUCTORS +# pragma GCC diagnostic ignored "-Wexit-time-destructors" +# endif +#endif const Feature Client::TRACKING = "TRACKING"; +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_EXIT_TIME_DESTRUCTORS || defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_GLOBAL_CONSTRUCTORS) +#pragma GCC diagnostic pop +#endif Client::Client() { @@ -427,7 +468,7 @@ Device Client::getDevice(const std::string& name) if(hasDevice(name)) return Device(this, name); else - return Device(NULL, ""); + return Device(nullptr, ""); } std::set Client::getDevices() @@ -587,7 +628,7 @@ Device TcpClient::getDevice(const std::string& name) catch(NutException& ex) { if(ex.str()=="UNKNOWN-UPS") - return Device(NULL, ""); + return Device(nullptr, ""); else throw; } @@ -697,7 +738,7 @@ std::map > > TcpClient { std::vector& vals = *it2; std::string var = vals[0]; - vals.erase(vals.begin()), + vals.erase(vals.begin()); map2[var] = vals; } map[*it] = map2; @@ -1116,7 +1157,7 @@ Client* Device::getClient() bool Device::isOk()const { - return _client!=NULL && !_name.empty(); + return _client!=nullptr && !_name.empty(); } Device::operator bool()const @@ -1189,7 +1230,7 @@ Variable Device::getVariable(const std::string& name) if(getClient()->hasDeviceVariable(getName(), name)) return Variable(this, name); else - return Variable(NULL, ""); + return Variable(nullptr, ""); } std::set Device::getVariables() @@ -1245,7 +1286,7 @@ Command Device::getCommand(const std::string& name) if(getClient()->hasDeviceCommand(getName(), name)) return Command(this, name); else - return Command(NULL, ""); + return Command(nullptr, ""); } TrackingID Device::executeCommand(const std::string& name, const std::string& param) @@ -1326,7 +1367,7 @@ Device* Variable::getDevice() bool Variable::isOk()const { - return _device!=NULL && !_name.empty(); + return _device!=nullptr && !_name.empty(); } @@ -1421,7 +1462,7 @@ Device* Command::getDevice() bool Command::isOk()const { - return _device!=NULL && !_name.empty(); + return _device!=nullptr && !_name.empty(); } Command::operator bool()const @@ -1463,17 +1504,17 @@ void Command::execute(const std::string& param) extern "C" { -strarr strarr_alloc(unsigned short count) +strarr strarr_alloc(size_t count) { - strarr arr = (strarr)xcalloc(count+1, sizeof(char*)); - arr[count] = NULL; + strarr arr = static_cast(xcalloc(count+1, sizeof(char*))); + arr[count] = nullptr; return arr; } void strarr_free(strarr arr) { char** pstr = arr; - while(*pstr!=NULL) + while(*pstr!=nullptr) { free(*pstr); ++pstr; @@ -1511,13 +1552,14 @@ NUTCLIENT_TCP_t nutclient_tcp_create_client(const char* host, unsigned short por try { client->connect(host, port); - return (NUTCLIENT_TCP_t)client; + return static_cast(client); } catch(nut::NutException& ex) { // TODO really catch it + NUT_UNUSED_VARIABLE(ex); delete client; - return NULL; + return nullptr; } } @@ -1526,7 +1568,7 @@ void nutclient_destroy(NUTCLIENT_t client) { if(client) { - delete (nut::Client*)client; + delete static_cast(client); } } @@ -1534,7 +1576,7 @@ int nutclient_tcp_is_connected(NUTCLIENT_TCP_t client) { if(client) { - nut::TcpClient* cl = dynamic_cast((nut::Client*)client); + nut::TcpClient* cl = dynamic_cast(static_cast(client)); if(cl) { return cl->isConnected() ? 1 : 0; @@ -1547,7 +1589,7 @@ void nutclient_tcp_disconnect(NUTCLIENT_TCP_t client) { if(client) { - nut::TcpClient* cl = dynamic_cast((nut::Client*)client); + nut::TcpClient* cl = dynamic_cast(static_cast(client)); if(cl) { cl->disconnect(); @@ -1559,7 +1601,7 @@ int nutclient_tcp_reconnect(NUTCLIENT_TCP_t client) { if(client) { - nut::TcpClient* cl = dynamic_cast((nut::Client*)client); + nut::TcpClient* cl = dynamic_cast(static_cast(client)); if(cl) { try @@ -1577,7 +1619,7 @@ void nutclient_tcp_set_timeout(NUTCLIENT_TCP_t client, long timeout) { if(client) { - nut::TcpClient* cl = dynamic_cast((nut::Client*)client); + nut::TcpClient* cl = dynamic_cast(static_cast(client)); if(cl) { cl->setTimeout(timeout); @@ -1589,7 +1631,7 @@ long nutclient_tcp_get_timeout(NUTCLIENT_TCP_t client) { if(client) { - nut::TcpClient* cl = dynamic_cast((nut::Client*)client); + nut::TcpClient* cl = dynamic_cast(static_cast(client)); if(cl) { return cl->getTimeout(); @@ -1602,7 +1644,7 @@ void nutclient_authenticate(NUTCLIENT_t client, const char* login, const char* p { if(client) { - nut::Client* cl = (nut::Client*)client; + nut::Client* cl = static_cast(client); if(cl) { try @@ -1618,7 +1660,7 @@ void nutclient_logout(NUTCLIENT_t client) { if(client) { - nut::Client* cl = (nut::Client*)client; + nut::Client* cl = static_cast(client); if(cl) { try @@ -1634,7 +1676,7 @@ void nutclient_device_login(NUTCLIENT_t client, const char* dev) { if(client) { - nut::Client* cl = (nut::Client*)client; + nut::Client* cl = static_cast(client); if(cl) { try @@ -1650,7 +1692,7 @@ int nutclient_get_device_num_logins(NUTCLIENT_t client, const char* dev) { if(client) { - nut::Client* cl = (nut::Client*)client; + nut::Client* cl = static_cast(client); if(cl) { try @@ -1667,7 +1709,7 @@ void nutclient_device_master(NUTCLIENT_t client, const char* dev) { if(client) { - nut::Client* cl = (nut::Client*)client; + nut::Client* cl = static_cast(client); if(cl) { try @@ -1683,7 +1725,7 @@ void nutclient_device_forced_shutdown(NUTCLIENT_t client, const char* dev) { if(client) { - nut::Client* cl = (nut::Client*)client; + nut::Client* cl = static_cast(client); if(cl) { try @@ -1699,7 +1741,7 @@ strarr nutclient_get_devices(NUTCLIENT_t client) { if(client) { - nut::Client* cl = (nut::Client*)client; + nut::Client* cl = static_cast(client); if(cl) { try @@ -1709,14 +1751,14 @@ strarr nutclient_get_devices(NUTCLIENT_t client) catch(...){} } } - return NULL; + return nullptr; } int nutclient_has_device(NUTCLIENT_t client, const char* dev) { if(client) { - nut::Client* cl = (nut::Client*)client; + nut::Client* cl = static_cast(client); if(cl) { try @@ -1733,7 +1775,7 @@ char* nutclient_get_device_description(NUTCLIENT_t client, const char* dev) { if(client) { - nut::Client* cl = (nut::Client*)client; + nut::Client* cl = static_cast(client); if(cl) { try @@ -1743,14 +1785,14 @@ char* nutclient_get_device_description(NUTCLIENT_t client, const char* dev) catch(...){} } } - return NULL; + return nullptr; } strarr nutclient_get_device_variables(NUTCLIENT_t client, const char* dev) { if(client) { - nut::Client* cl = (nut::Client*)client; + nut::Client* cl = static_cast(client); if(cl) { try @@ -1760,14 +1802,14 @@ strarr nutclient_get_device_variables(NUTCLIENT_t client, const char* dev) catch(...){} } } - return NULL; + return nullptr; } strarr nutclient_get_device_rw_variables(NUTCLIENT_t client, const char* dev) { if(client) { - nut::Client* cl = (nut::Client*)client; + nut::Client* cl = static_cast(client); if(cl) { try @@ -1777,14 +1819,14 @@ strarr nutclient_get_device_rw_variables(NUTCLIENT_t client, const char* dev) catch(...){} } } - return NULL; + return nullptr; } int nutclient_has_device_variable(NUTCLIENT_t client, const char* dev, const char* var) { if(client) { - nut::Client* cl = (nut::Client*)client; + nut::Client* cl = static_cast(client); if(cl) { try @@ -1801,7 +1843,7 @@ char* nutclient_get_device_variable_description(NUTCLIENT_t client, const char* { if(client) { - nut::Client* cl = (nut::Client*)client; + nut::Client* cl = static_cast(client); if(cl) { try @@ -1811,14 +1853,14 @@ char* nutclient_get_device_variable_description(NUTCLIENT_t client, const char* catch(...){} } } - return NULL; + return nullptr; } strarr nutclient_get_device_variable_values(NUTCLIENT_t client, const char* dev, const char* var) { if(client) { - nut::Client* cl = (nut::Client*)client; + nut::Client* cl = static_cast(client); if(cl) { try @@ -1828,14 +1870,14 @@ strarr nutclient_get_device_variable_values(NUTCLIENT_t client, const char* dev, catch(...){} } } - return NULL; + return nullptr; } void nutclient_set_device_variable_value(NUTCLIENT_t client, const char* dev, const char* var, const char* value) { if(client) { - nut::Client* cl = (nut::Client*)client; + nut::Client* cl = static_cast(client); if(cl) { try @@ -1851,13 +1893,13 @@ void nutclient_set_device_variable_values(NUTCLIENT_t client, const char* dev, c { if(client) { - nut::Client* cl = (nut::Client*)client; + nut::Client* cl = static_cast(client); if(cl) { try { std::vector vals; - strarr pstr = (strarr)values; + strarr pstr = static_cast(values); while(*pstr) { vals.push_back(std::string(*pstr)); @@ -1875,7 +1917,7 @@ strarr nutclient_get_device_commands(NUTCLIENT_t client, const char* dev) { if(client) { - nut::Client* cl = (nut::Client*)client; + nut::Client* cl = static_cast(client); if(cl) { try @@ -1885,14 +1927,14 @@ strarr nutclient_get_device_commands(NUTCLIENT_t client, const char* dev) catch(...){} } } - return NULL; + return nullptr; } int nutclient_has_device_command(NUTCLIENT_t client, const char* dev, const char* cmd) { if(client) { - nut::Client* cl = (nut::Client*)client; + nut::Client* cl = static_cast(client); if(cl) { try @@ -1909,7 +1951,7 @@ char* nutclient_get_device_command_description(NUTCLIENT_t client, const char* d { if(client) { - nut::Client* cl = (nut::Client*)client; + nut::Client* cl = static_cast(client); if(cl) { try @@ -1919,14 +1961,14 @@ char* nutclient_get_device_command_description(NUTCLIENT_t client, const char* d catch(...){} } } - return NULL; + return nullptr; } void nutclient_execute_device_command(NUTCLIENT_t client, const char* dev, const char* cmd, const char* param) { if(client) { - nut::Client* cl = (nut::Client*)client; + nut::Client* cl = static_cast(client); if(cl) { try @@ -1938,4 +1980,8 @@ void nutclient_execute_device_command(NUTCLIENT_t client, const char* dev, const } } +#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_CXX98_COMPAT +#pragma GCC diagnostic pop +#endif + } /* extern "C" */ diff --git a/clients/nutclient.h b/clients/nutclient.h index db928575a9..db9d0f6877 100644 --- a/clients/nutclient.h +++ b/clients/nutclient.h @@ -51,7 +51,7 @@ class NutException : public std::exception { public: NutException(const std::string& msg):_msg(msg){} - virtual ~NutException() {} + virtual ~NutException(); virtual const char * what() const noexcept {return this->_msg.c_str();} virtual std::string str() const noexcept {return this->_msg;} private: @@ -65,7 +65,7 @@ class SystemException : public NutException { public: SystemException(); - virtual ~SystemException() {} + virtual ~SystemException(); private: static std::string err(); }; @@ -78,7 +78,7 @@ class IOException : public NutException { public: IOException(const std::string& msg):NutException(msg){} - virtual ~IOException() {} + virtual ~IOException(); }; /** @@ -88,7 +88,7 @@ class UnknownHostException : public IOException { public: UnknownHostException():IOException("Unknown host"){} - virtual ~UnknownHostException() {} + virtual ~UnknownHostException(); }; /** @@ -98,7 +98,7 @@ class NotConnectedException : public IOException { public: NotConnectedException():IOException("Not connected"){} - virtual ~NotConnectedException() {} + virtual ~NotConnectedException(); }; /** @@ -108,7 +108,7 @@ class TimeoutException : public IOException { public: TimeoutException():IOException("Timeout"){} - virtual ~TimeoutException() {} + virtual ~TimeoutException(); }; /** @@ -151,13 +151,13 @@ class Client * \todo Is his method is global to all connection protocol or is it specific to TCP ? * \note Actually, authentication fails only if already set, not if bad values are sent. */ - virtual void authenticate(const std::string& user, const std::string& passwd)=0; + virtual void authenticate(const std::string& user, const std::string& passwd) = 0; /** * Disconnect from the NUTD server. * \todo Is his method is global to all connection protocol or is it specific to TCP ? */ - virtual void logout()=0; + virtual void logout() = 0; /** * Device manipulations. @@ -186,13 +186,13 @@ class Client * Retrieve names of devices supported by NUTD server. * \return The set of names of supported devices. */ - virtual std::set getDeviceNames()=0; + virtual std::set getDeviceNames() = 0; /** * Retrieve the description of a device. * \param name Device name. * \return Device description. */ - virtual std::string getDeviceDescription(const std::string& name)=0; + virtual std::string getDeviceDescription(const std::string& name) = 0; /** \} */ /** @@ -205,13 +205,13 @@ class Client * \param dev Device name * \return Variable names */ - virtual std::set getDeviceVariableNames(const std::string& dev)=0; + virtual std::set getDeviceVariableNames(const std::string& dev) = 0; /** * Retrieve names of read/write variables supported by a device. * \param dev Device name * \return RW variable names */ - virtual std::set getDeviceRWVariableNames(const std::string& dev)=0; + virtual std::set getDeviceRWVariableNames(const std::string& dev) = 0; /** * Test if a variable is supported by a device. * \param dev Device name @@ -225,14 +225,14 @@ class Client * \param name Variable name * \return Variable description if provided. */ - virtual std::string getDeviceVariableDescription(const std::string& dev, const std::string& name)=0; + virtual std::string getDeviceVariableDescription(const std::string& dev, const std::string& name) = 0; /** * Retrieve values of a variable. * \param dev Device name * \param name Variable name * \return Variable values (usually one) if available. */ - virtual std::vector getDeviceVariableValue(const std::string& dev, const std::string& name)=0; + virtual std::vector getDeviceVariableValue(const std::string& dev, const std::string& name) = 0; /** * Retrieve values of all variables of a device. * \param dev Device name @@ -251,14 +251,14 @@ class Client * \param name Variable name * \param value Variable value */ - virtual TrackingID setDeviceVariable(const std::string& dev, const std::string& name, const std::string& value)=0; + virtual TrackingID setDeviceVariable(const std::string& dev, const std::string& name, const std::string& value) = 0; /** * Intend to set the value of a variable. * \param dev Device name * \param name Variable name - * \param value Variable value + * \param values Vector of variable values */ - virtual TrackingID setDeviceVariable(const std::string& dev, const std::string& name, const std::vector& values)=0; + virtual TrackingID setDeviceVariable(const std::string& dev, const std::string& name, const std::vector& values) = 0; /** \} */ /** @@ -271,7 +271,7 @@ class Client * \param dev Device name * \return Command names */ - virtual std::set getDeviceCommandNames(const std::string& dev)=0; + virtual std::set getDeviceCommandNames(const std::string& dev) = 0; /** * Test if a command is supported by a device. * \param dev Device name @@ -285,14 +285,14 @@ class Client * \param name Command name * \return Command description if provided. */ - virtual std::string getDeviceCommandDescription(const std::string& dev, const std::string& name)=0; + virtual std::string getDeviceCommandDescription(const std::string& dev, const std::string& name) = 0; /** * Intend to execute a command. * \param dev Device name * \param name Command name * \param param Additional command parameter */ - virtual TrackingID executeDeviceCommand(const std::string& dev, const std::string& name, const std::string& param="")=0; + virtual TrackingID executeDeviceCommand(const std::string& dev, const std::string& name, const std::string& param="") = 0; /** \} */ /** @@ -303,25 +303,25 @@ class Client * Log the current user (if authenticated) for a device. * \param dev Device name. */ - virtual void deviceLogin(const std::string& dev)=0; + virtual void deviceLogin(const std::string& dev) = 0; /** * Retrieve the number of user longged in the specified device. * \param dev Device name. * \return Number of logged-in users. */ - virtual int deviceGetNumLogins(const std::string& dev)=0; - virtual void deviceMaster(const std::string& dev)=0; - virtual void deviceForcedShutdown(const std::string& dev)=0; + virtual int deviceGetNumLogins(const std::string& dev) = 0; + virtual void deviceMaster(const std::string& dev) = 0; + virtual void deviceForcedShutdown(const std::string& dev) = 0; /** * Retrieve the result of a tracking ID. * \param id Tracking ID. */ - virtual TrackingResult getTrackingResult(const TrackingID& id)=0; + virtual TrackingResult getTrackingResult(const TrackingID& id) = 0; virtual bool hasFeature(const Feature& feature); - virtual bool isFeatureEnabled(const Feature& feature)=0; - virtual void setFeature(const Feature& feature, bool status)=0; + virtual bool isFeatureEnabled(const Feature& feature) = 0; + virtual void setFeature(const Feature& feature, bool status) = 0; static const Feature TRACKING; @@ -537,7 +537,7 @@ class Device /** * Intend to set values of a variable of the device. * \param name Variable name. - * \param value New variable values. + * \param values Vector of new variable values. */ void setVariable(const std::string& name, const std::vector& values); @@ -672,7 +672,7 @@ class Variable void setValue(const std::string& value); /** * Intend to set (multiple) values to the variable. - * \param value New variable values. + * \param values Vector of new variable values. */ void setValues(const std::vector& values); @@ -745,9 +745,8 @@ class Command std::string getDescription(); /** - * Intend to retrieve command description. - * \param param Additional command parameter - * \return Command description if provided. + * Intend to execute the instant command on device. + * \param param Optional additional command parameter */ void execute(const std::string& param=""); @@ -782,7 +781,7 @@ typedef char** strarr; /** * Alloc an array of string. */ -strarr strarr_alloc(unsigned short count); +strarr strarr_alloc(size_t count); /** * Free an array of string. @@ -986,7 +985,7 @@ typedef NUTCLIENT_t NUTCLIENT_TCP_t; * Create a client to NUTD using a TCP connection. * \param host Host name to connect to. * \param port Host port. - * \return New client or NULL if failed. + * \return New client or nullptr if failed. */ NUTCLIENT_TCP_t nutclient_tcp_create_client(const char* host, unsigned short port); /** diff --git a/configure.ac b/configure.ac index bb2d03737d..e7918d16ac 100644 --- a/configure.ac +++ b/configure.ac @@ -142,6 +142,7 @@ dnl Note: the compiler/pragma/attr methods below are custom for NUT codebase: NUT_COMPILER_FAMILY AX_C_PRAGMAS AX_C___ATTRIBUTE__ +AX_C_PRINTF_STRING_NULL AC_CHECK_FUNCS(flock lockf fcvt fcvtl pow10 round abs_val abs) AC_CHECK_FUNCS(fabs, [], [], [#include ]) AC_CHECK_FUNCS(cfsetispeed tcsendbreak) diff --git a/include/str.h b/include/str.h index 028d504a00..92edbda257 100644 --- a/include/str.h +++ b/include/str.h @@ -29,6 +29,15 @@ extern "C" { /* *INDENT-ON* */ #endif +/* Some compilers and/or C libraries do not handle printf("%s", NULL) correctly */ +#ifndef NUT_STRARG +# ifdef HAVE_PRINTF_STRING_NULL +# define NUT_STRARG(x) x +# else +# define NUT_STRARG(x) (x?x:"(null)") +# endif +#endif + /* Remove all * - leading and trailing (str_trim[_m]()) * - leading (str_ltrim[_m]()) diff --git a/m4/ax_c_pragmas.m4 b/m4/ax_c_pragmas.m4 index e58513b727..db9546882e 100644 --- a/m4/ax_c_pragmas.m4 +++ b/m4/ax_c_pragmas.m4 @@ -3,16 +3,23 @@ dnl e.g. diagnostics management to keep warnings quiet sometimes AC_DEFUN([AX_C_PRAGMAS], [ CFLAGS_SAVED="${CFLAGS}" + CXXFLAGS_SAVED="${CXXFLAGS}" + + dnl ### To be sure, bolt the language + AC_LANG_PUSH([C]) dnl # This is expected to fail builds with unknown pragma names on GCC or CLANG at least AS_IF([test "${CLANG}" = "yes"], - [CFLAGS="${CFLAGS_SAVED} -Werror=pragmas -Werror=unknown-warning-option"], + [CFLAGS="${CFLAGS_SAVED} -Werror=pragmas -Werror=unknown-warning-option" + CXXFLAGS="${CXXFLAGS_SAVED} -Werror=pragmas -Werror=unknown-warning-option"], [AS_IF([test "${GCC}" = "yes"], dnl ### Despite the docs, this dies with lack of (apparently) support for dnl ### -Wunknown-warning(-options) on all GCC versions I tried (v5-v10) dnl ### [CFLAGS="${CFLAGS_SAVED} -Werror=pragmas -Werror=unknown-warning"], - [CFLAGS="${CFLAGS_SAVED} -Wall -Wextra -Werror"], - [CFLAGS="${CFLAGS_SAVED} -Wall -Wextra -Werror"]) + [CFLAGS="${CFLAGS_SAVED} -Wall -Wextra -Werror" + CXXFLAGS="${CXXFLAGS_SAVED} -Wall -Wextra -Werror"], + [CFLAGS="${CFLAGS_SAVED} -Wall -Wextra -Werror" + CXXFLAGS="${CXXFLAGS_SAVED} -Wall -Wextra -Werror"]) ]) AC_CACHE_CHECK([for pragma GCC diagnostic push and pop], @@ -78,6 +85,73 @@ dnl ### [CFLAGS="${CFLAGS_SAVED} -Werror=pragmas -Werror=unknown-warning" AC_DEFINE([HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE], 1, [define if your compiler has #pragma GCC diagnostic ignored "-Wunreachable-code"]) ]) + AC_CACHE_CHECK([for pragma GCC diagnostic ignored "-Wformat-overflow"], + [ax_cv__pragma__gcc__diags_ignored_format_overflow], + [AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[#pragma GCC diagnostic ignored "-Wformat-overflow"]], [])], + [ax_cv__pragma__gcc__diags_ignored_format_overflow=yes], + [ax_cv__pragma__gcc__diags_ignored_format_overflow=no] + )] + ) + AS_IF([test "$ax_cv__pragma__gcc__diags_ignored_format_overflow" = "yes"],[ + AC_DEFINE([HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_OVERFLOW], 1, [define if your compiler has #pragma GCC diagnostic ignored "-Wformat-overflow"]) + ]) + + AC_LANG_POP([C]) + + dnl ### Series of tests for C++ specific pragmas + AC_LANG_PUSH([C++]) + + AC_CACHE_CHECK([for C++ pragma GCC diagnostic ignored "-Wc++98-compat-pedantic"], + [ax_cv__pragma__gcc__diags_ignored_cxx98_compat_pedantic], + [AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[#pragma GCC diagnostic ignored "-Wc++98-compat-pedantic"]], [])], + [ax_cv__pragma__gcc__diags_ignored_cxx98_compat_pedantic=yes], + [ax_cv__pragma__gcc__diags_ignored_cxx98_compat_pedantic=no] + )] + ) + AS_IF([test "$ax_cv__pragma__gcc__diags_ignored_cxx98_compat_pedantic" = "yes"],[ + AC_DEFINE([HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_CXX98_COMPAT_PEDANTIC], 1, [define if your compiler has #pragma GCC diagnostic ignored "-Wc++98-compat-pedantic"]) + ]) + + AC_CACHE_CHECK([for C++ pragma GCC diagnostic ignored "-Wc++98-compat"], + [ax_cv__pragma__gcc__diags_ignored_cxx98_compat], + [AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[#pragma GCC diagnostic ignored "-Wc++98-compat"]], [])], + [ax_cv__pragma__gcc__diags_ignored_cxx98_compat=yes], + [ax_cv__pragma__gcc__diags_ignored_cxx98_compat=no] + )] + ) + AS_IF([test "$ax_cv__pragma__gcc__diags_ignored_cxx98_compat" = "yes"],[ + AC_DEFINE([HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_CXX98_COMPAT], 1, [define if your compiler has #pragma GCC diagnostic ignored "-Wc++98-compat"]) + ]) + + AC_CACHE_CHECK([for C++ pragma GCC diagnostic ignored "-Wglobal-constructors"], + [ax_cv__pragma__gcc__diags_ignored_global_constructors], + [AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[#pragma GCC diagnostic ignored "-Wglobal-constructors"]], [])], + [ax_cv__pragma__gcc__diags_ignored_global_constructors=yes], + [ax_cv__pragma__gcc__diags_ignored_global_constructors=no] + )] + ) + AS_IF([test "$ax_cv__pragma__gcc__diags_ignored_global_constructors" = "yes"],[ + AC_DEFINE([HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_GLOBAL_CONSTRUCTORS], 1, [define if your compiler has #pragma GCC diagnostic ignored "-Wglobal-constructors"]) + ]) + + AC_CACHE_CHECK([for C++ pragma GCC diagnostic ignored "-Wexit-time-destructors"], + [ax_cv__pragma__gcc__diags_ignored_exit_time_destructors], + [AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[#pragma GCC diagnostic ignored "-Wexit-time-destructors"]], [])], + [ax_cv__pragma__gcc__diags_ignored_exit_time_destructors=yes], + [ax_cv__pragma__gcc__diags_ignored_exit_time_destructors=no] + )] + ) + AS_IF([test "$ax_cv__pragma__gcc__diags_ignored_exit_time_destructors" = "yes"],[ + AC_DEFINE([HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_EXIT_TIME_DESTRUCTORS], 1, [define if your compiler has #pragma GCC diagnostic ignored "-Wexit-time-destructors"]) + ]) + + AC_LANG_POP([C++]) + dnl # Meta-macros for simpler use-cases where we pick dnl # equivalent-effect macros for different compiler versions AS_IF([test "$ax_cv__pragma__gcc__diags_push_pop" = "yes"],[ @@ -87,6 +161,9 @@ dnl ### [CFLAGS="${CFLAGS_SAVED} -Werror=pragmas -Werror=unknown-warning" AS_IF([test "$ax_cv__pragma__gcc__diags_ignored_unreachable_code" = "yes" || test "$ax_cv__pragma__gcc__diags_ignored_unreachable_code_break" = "yes" ],[ AC_DEFINE([HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE], 1, [define if your compiler has pragmas for GCC diagnostic ignored "-Wunreachable-code(-break)" and for push-pop support]) ]) + AS_IF([test "$ax_cv__pragma__gcc__diags_ignored_cxx98_compat_pedantic" = "yes" || test "$ax_cv__pragma__gcc__diags_ignored_cxx98_compat" = "yes" ],[ + AC_DEFINE([HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_CXX98_COMPAT], 1, [define if your compiler has pragmas for GCC diagnostic ignored "-Wc++98-compat(-pedantic)" and for push-pop support]) + ]) ]) dnl ### Sanity check if the CLI options actually work: @@ -115,4 +192,47 @@ dnl ### [CFLAGS="${CFLAGS_SAVED} -Werror=pragmas -Werror=unknown-warning" [AC_MSG_WARN([A bogus test that was expected to fail did not! ax_cv__pragma__bogus_diag=$ax_cv__pragma__bogus_diag (not 'no')])]) CFLAGS="${CFLAGS_SAVED}" + CXXFLAGS="${CXXFLAGS_SAVED}" +]) + +AC_DEFUN([AX_C_PRINTF_STRING_NULL], [ + dnl ### To be sure, bolt the language + AC_LANG_PUSH([C]) + + AC_CACHE_CHECK([for practical support to pritnf("%s", NULL)], + [ax_cv__printf_string_null], + [AC_RUN_IFELSE( + [AC_LANG_PROGRAM([dnl +#include +#include +], [dnl +char buf[128]; +char *s = NULL; +int res = snprintf(buf, sizeof(buf), "%s", s); +buf[sizeof(buf)-1] = '\0'; +if (res < 0) { + printf(stderr, "FAILED to snprintf() a NULL string argument"); + exit 1; +} +if (buf[0] == '\0') + printf(stderr, "RETURNED empty string from snprintf() with a NULL string argument"); + exit 0; +} +if (strcasestr(buf, 'null') == NULL) + printf(stderr, "RETURNED some string from snprintf() with a NULL string argument: '%s'", buf); + exit 0; +} +printf(stderr, "SUCCESS: RETURNED a string that contains something like 'null' from snprintf() with a NULL string argument: '%s'", buf); +exit 0; + ])], + [ax_cv__printf_string_null=yes], + [ax_cv__printf_string_null=no] + )] + ) + + AS_IF([test "$ax_cv__printf_string_null" = "yes"],[ + AC_DEFINE([HAVE_PRINTF_STRING_NULL], 1, [define if your libc can printf("%s", NULL) sanely]) + ]) + + AC_LANG_POP([C]) ]) diff --git a/tests/Makefile.am b/tests/Makefile.am index 8b936e07d3..7c7d8aedc3 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -15,6 +15,11 @@ nutlogtest_SOURCES = nutlogtest.c nutlogtest_LDADD = $(top_builddir)/common/libcommon.la ### Optional tests which can not be built everywhere +# List of src files for CppUnit tests +CPPUNITTESTSRC = example.cpp nutclienttest.cpp +# The test driver which orchestrates running those tests above +CPPUNITTESTERSRC = cpputest.cpp + if HAVE_CXX11 if HAVE_CPPUNIT # Note: per configure script this "SHOULD" also assume @@ -30,16 +35,12 @@ endif cppunittest_CXXFLAGS = $(AM_CXXFLAGS) $(CPPUNIT_CFLAGS) $(CPPUNIT_CXXFLAGS) $(CPPUNIT_NUT_CXXFLAGS) $(CXXFLAGS) cppunittest_LDFLAGS = $(CPPUNIT_LIBS) cppunittest_LDADD = $(top_builddir)/clients/libnutclient.la - -# List of src files for CppUnit tests -CPPUNITTESTSRC = example.cpp nutclienttest.cpp - -cppunittest_SOURCES = $(CPPUNITTESTSRC) cpputest.cpp +cppunittest_SOURCES = $(CPPUNITTESTSRC) $(CPPUNITTESTERSRC) else !HAVE_CPPUNIT # Just redistribute test source into tarball -EXTRA_DIST += example.cpp cpputest.cpp +EXTRA_DIST += $(CPPUNITTESTSRC) $(CPPUNITTESTERSRC) endif !HAVE_CPPUNIT diff --git a/tests/cpputest.cpp b/tests/cpputest.cpp index a30250975e..1168f2562d 100644 --- a/tests/cpputest.cpp +++ b/tests/cpputest.cpp @@ -2,6 +2,7 @@ Copyright (C) 2012 Emilien Kia + 2020 Jim Klimov 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 @@ -58,7 +59,6 @@ int main(int argc, char* argv[]) /* Return error code 1 if the one of test failed. */ std::cerr << "D: Got to the end of test suite with code " << - "'" << wasSucessful << "'" << std::endl; + "'" << ( wasSucessful ? "true" : "false" ) << "'" << std::endl; return wasSucessful ? 0 : 1; } - diff --git a/tests/example.cpp b/tests/example.cpp index 28ea52e318..e06bf7c9b9 100644 --- a/tests/example.cpp +++ b/tests/example.cpp @@ -2,6 +2,7 @@ Copyright (C) 2012 Emilien Kia + 2020 Jim Klimov 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 @@ -17,6 +18,20 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "common.h" + +/* Current CPPUnit offends the honor of C++98 */ +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_EXIT_TIME_DESTRUCTORS || defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_GLOBAL_CONSTRUCTORS) +#pragma GCC diagnostic push +# ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_GLOBAL_CONSTRUCTORS +# pragma GCC diagnostic ignored "-Wglobal-constructors" +# endif +# ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_EXIT_TIME_DESTRUCTORS +# pragma GCC diagnostic ignored "-Wexit-time-destructors" +# endif +#endif + #include class ExampleTest : public CppUnit::TestFixture @@ -53,10 +68,12 @@ void ExampleTest::testOne() float f = 1.0; // Process - int cast = (int)f; + int cast = static_cast(f); // Check CPPUNIT_ASSERT_EQUAL( i, cast ); } - +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_EXIT_TIME_DESTRUCTORS || defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_GLOBAL_CONSTRUCTORS) +#pragma GCC diagnostic pop +#endif diff --git a/tests/nutclienttest.cpp b/tests/nutclienttest.cpp index d8cb942f5f..7b0b11b712 100644 --- a/tests/nutclienttest.cpp +++ b/tests/nutclienttest.cpp @@ -1,6 +1,7 @@ /* nutclienttest - CppUnit nutclient unit test Copyright (C) 2016 Emilien Kia + Copyright (C) 2020 Jim Klimov 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 @@ -16,6 +17,20 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "common.h" + +/* Current CPPUnit offends the honor of C++98 */ +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_EXIT_TIME_DESTRUCTORS || defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_GLOBAL_CONSTRUCTORS) +#pragma GCC diagnostic push +# ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_GLOBAL_CONSTRUCTORS +# pragma GCC diagnostic ignored "-Wglobal-constructors" +# endif +# ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_EXIT_TIME_DESTRUCTORS +# pragma GCC diagnostic ignored "-Wexit-time-destructors" +# endif +#endif + #include namespace nut { @@ -87,18 +102,18 @@ void NutClientTest::test_stringset_to_strarr() strset.insert("world"); strarr arr = stringset_to_strarr(strset); - CPPUNIT_ASSERT_MESSAGE("stringset_to_strarr(...) result is null", arr!=NULL); + CPPUNIT_ASSERT_MESSAGE("stringset_to_strarr(...) result is null", arr != nullptr); std::set res; char** ptr = arr; - while(*ptr!=NULL) + while(*ptr != nullptr) { res.insert(std::string(*ptr)); ptr++; } - CPPUNIT_ASSERT_EQUAL_MESSAGE("stringset_to_strarr(...) result has not 3 items", (size_t)3, res.size()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("stringset_to_strarr(...) result has not 3 items", static_cast(3), res.size()); CPPUNIT_ASSERT_MESSAGE("stringset_to_strarr(...) result has not item \"test\"", res.find("test")!=res.end()); CPPUNIT_ASSERT_MESSAGE("stringset_to_strarr(...) result has not item \"hello\"", res.find("hello")!=res.end()); CPPUNIT_ASSERT_MESSAGE("stringset_to_strarr(...) result has not item \"world\"", res.find("world")!=res.end()); @@ -114,7 +129,7 @@ void NutClientTest::test_stringvector_to_strarr() strset.push_back("world"); strarr arr = stringvector_to_strarr(strset); - CPPUNIT_ASSERT_MESSAGE("stringvector_to_strarr(...) result is null", arr!=NULL); + CPPUNIT_ASSERT_MESSAGE("stringvector_to_strarr(...) result is null", arr != nullptr); char** ptr = arr; CPPUNIT_ASSERT_EQUAL_MESSAGE("stringvector_to_strarr(...) result has not item 0==\"test\"", std::string("test"), std::string(*ptr)); @@ -123,7 +138,12 @@ void NutClientTest::test_stringvector_to_strarr() ++ptr; CPPUNIT_ASSERT_EQUAL_MESSAGE("stringvector_to_strarr(...) result has not item 2==\"world\"", std::string("world"), std::string(*ptr)); ++ptr; - CPPUNIT_ASSERT_EQUAL_MESSAGE("stringvector_to_strarr(...) result has not only 3 items", (char*)NULL, *ptr); + + /* https://stackoverflow.com/a/12565009/4715872 + * Can not compare nullptr_t and another data type (char*) + * with CPPUNIT template assertEquals() + */ + CPPUNIT_ASSERT_MESSAGE("stringvector_to_strarr(...) result has not only 3 items", nullptr == *ptr); strarr_free(arr); } @@ -197,3 +217,7 @@ void NutClientTest::test_copy_assignment_var() { } } // namespace nut {} + +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_EXIT_TIME_DESTRUCTORS || defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_GLOBAL_CONSTRUCTORS) +#pragma GCC diagnostic pop +#endif diff --git a/tests/nutlogtest.c b/tests/nutlogtest.c index 0a4c49728a..68c6940b32 100644 --- a/tests/nutlogtest.c +++ b/tests/nutlogtest.c @@ -1,12 +1,56 @@ /* nutlogtest - some trivial usage for upslog*() and upsdebug*() related * routines to sanity-check their code (compiler does not warn, test runs * do not crash). + * + * Copyright (C) + * 2020 Jim Klimov + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common.h" int main(void) { const char *s1 = "!NULL"; const char *s2 = NULL; - upsdebugx(0, "D: '%s' vs '%s'", s1, s2); + +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_OVERFLOW) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wformat-overflow" +#endif + upsdebugx(0, "D: checking with libc handling of NULL: '%s' vs '%s'", s1, s2); +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_OVERFLOW) +# pragma GCC diagnostic pop +#endif + +/* This explicitly does not work with -Wformat, due to verbatim NULL without a var: + * nutlogtest.c:20:5: error: reading through null pointer (argument 4) [-Werror=format=] + * and also due to (void*) vs (char*) in naive case: + * upsdebugx(0, "D: '%s' vs '%s'", NUT_STRARG(NULL), NULL); + * but with casting the explicit NULL remains: + * upsdebugx(0, "D: '%s' vs '%s'", NUT_STRARG((char *)NULL), (char *)NULL); + */ + + upsdebugx(0, "D: checking with NUT_STRARG macro: '%s' vs '%s'", NUT_STRARG(s2), s2); + +#ifdef NUT_STRARG +#undef NUT_STRARG +#endif + +#define NUT_STRARG(x) (x?x:"") + + upsdebugx(0, "D: checking that macro wrap trick works: '%s' vs '%s'", NUT_STRARG(s2), s2); + return 0; }