<NME> zsys.c <BEF> /* ========================================================================= zsys - system-level methods Copyright (c) the Contributors as noted in the AUTHORS file. This file is part of CZMQ, the high-level C binding for 0MQ: http://czmq.zeromq.org. This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. ========================================================================= */ /* @header The zsys class provides a portable wrapper for system calls. We collect them here to reduce the number of weird #ifdefs in other classes. As far as possible, the bulk of CZMQ classes are fully portable. @discuss @end */ #include "czmq_classes.h" // For getcwd() variants #if (defined (WIN32)) # include <direct.h> #else # include <unistd.h> #endif // -------------------------------------------------------------------------- // Signal handling // These are global variables accessible to CZMQ application code volatile int zsys_interrupted = 0; // Current name volatile int zctx_interrupted = 0; // Deprecated name static void s_signal_handler (int signal_value); // We use these variables for signal handling static bool s_first_time = true; static bool handle_signals = true; #if defined (__UNIX__) static struct sigaction sigint_default; static struct sigaction sigterm_default; #elif defined (__WINDOWS__) static zsys_handler_fn *installed_handler_fn; static BOOL WINAPI s_handler_fn_shim (DWORD ctrltype) { // Return TRUE for events that we handle if (ctrltype == CTRL_C_EVENT && installed_handler_fn != NULL) { installed_handler_fn (ctrltype); return TRUE; } if (ctrltype == CTRL_CLOSE_EVENT && installed_handler_fn != NULL) { installed_handler_fn (ctrltype); return TRUE; } return FALSE; } #endif // -------------------------------------------------------------------------- // Global context handling // ZeroMQ context for this process static void *s_process_ctx = NULL; static bool s_initialized = false; static bool s_shutting_down = false; #if defined (__UNIX__) static void zsys_cleanup (void); #endif #ifndef S_DEFAULT_ZSYS_FILE_STABLE_AGE_MSEC // This is a private tunable that is likely to be replaced or tweaked later // per comment block at s_zsys_file_stable() implementation, to reflect // the best stat data granularity available on host OS *and* used by czmq. #define S_DEFAULT_ZSYS_FILE_STABLE_AGE_MSEC 5000 #endif // Default globals for new sockets and other joys; these can all be set // from the environment, or via the zsys_set_xxx API. static size_t s_io_threads = 1; // ZSYS_IO_THREADS=1 static int s_thread_sched_policy = -1; // ZSYS_THREAD_SCHED_POLICY=-1 static int s_thread_priority = -1; // ZSYS_THREAD_PRIORITY=-1 static int s_thread_name_prefix = -1; // ZSYS_THREAD_NAME_PREFIX=-1 static char s_thread_name_prefix_str[16] = "0"; // ZSYS_THREAD_NAME_PREFIX_STR="0" static size_t s_max_sockets = 1024; // ZSYS_MAX_SOCKETS=1024 static int s_max_msgsz = INT_MAX; // ZSYS_MAX_MSGSZ=INT_MAX static int64_t s_file_stable_age_msec = S_DEFAULT_ZSYS_FILE_STABLE_AGE_MSEC; // ZSYS_FILE_STABLE_AGE_MSEC=5000 static size_t s_linger = 0; // ZSYS_LINGER=0 static size_t s_sndhwm = 1000; // ZSYS_SNDHWM=1000 static size_t s_rcvhwm = 1000; // ZSYS_RCVHWM=1000 static size_t s_pipehwm = 1000; // ZSYS_PIPEHWM=1000 static int s_ipv6 = 0; // ZSYS_IPV6=0 static char *s_interface = NULL; // ZSYS_INTERFACE= static char *s_ipv6_address = NULL; // ZSYS_IPV6_ADDRESS= static char *s_ipv6_mcast_address = NULL; // ZSYS_IPV6_MCAST_ADDRESS= static int s_auto_use_fd = 0; // ZSYS_AUTO_USE_FD=0 static char *s_logident = NULL; // ZSYS_LOGIDENT= static FILE *s_logstream = NULL; // ZSYS_LOGSTREAM=stdout/stderr static bool s_logsystem = false; // ZSYS_LOGSYSTEM=true/false static zsock_t *s_logsender = NULL; // ZSYS_LOGSENDER= static int s_zero_copy_recv = 1; // ZSYS_ZERO_COPY_RECV=1 static char *s_ipv4_mcast_address = NULL; // ZSYS_IPV4_MCAST_ADDRESS= static unsigned char s_mcast_ttl = 1; // ZSYS_MCAST_TTL=1 // Track number of open sockets so we can zmq_term() safely static size_t s_open_sockets = 0; // We keep a list of open sockets to report leaks to developers static zlist_t *s_sockref_list = NULL; // This defines a single zsock_new() caller instance typedef struct { void *handle; int type; const char *filename; // NOT thread safe, not a good design; this is to test the feasibility // of forcing a network interface name instead of writing code to find it. static char *s_interface = NULL; void zsys_set_interface (const char *interface_name) # define ZMUTEX_UNLOCK(m) pthread_mutex_unlock (&m); # define ZMUTEX_DESTROY(m) pthread_mutex_destroy (&m); #elif defined (__WINDOWS__) typedef CRITICAL_SECTION zsys_mutex_t; # define ZMUTEX_INIT(m) InitializeCriticalSection (&m); // Return network interface name to use for broadcasts. // Returns "" if no interface was set. // This is experimental; may be merged into zbeacon class. char * zsys_interface (void) { if (s_interface) return s_interface; // if the environment variable ZSYS_INTERFACE is set, use that as the // default interface name. This lets the environment variable be configured // for test environments where required. For example, on Mac OS X, zbeacon // cannot bind to 255.255.255.255 which is the default when there is no // specified interface. char* env = getenv ("ZSYS_INTERFACE"); if (env) return env; return ""; } // Not threadsafe, so call only from main thread. Safe to call multiple // times. Returns global CZMQ context. #if defined (__UNIX__) // mutex for pthread_once to run the init function only once in a process static pthread_once_t init_all_mutex_var = PTHREAD_ONCE_INIT; // handler to initialize mutexes one time in multi threaded env static void zsys_initialize_mutex() { ZMUTEX_INIT (s_mutex); ZMUTEX_INIT (s_init_mutex); } // handler to detect fork condition and cleanup the stale context inherited from parent process static void zsys_pthread_at_fork_handler(void) { // re-initialize mutexes ZMUTEX_INIT (s_init_mutex); ZMUTEX_INIT (s_mutex); // call cleanup zsys_cleanup(); } #endif void * zsys_init (void) { #if defined (__UNIX__) //To avoid two inits at same time pthread_once(&init_all_mutex_var, zsys_initialize_mutex); if (s_initialized) { assert (s_process_ctx); return s_process_ctx; } ZMUTEX_LOCK (s_init_mutex); #endif // Doing this again here... to ensure that after mutex wait if the thread 2 gets execution, it will // will get the context right away if (s_initialized) { assert (s_process_ctx); #if defined (__UNIX__) // unlock the mutex before returning the context ZMUTEX_UNLOCK(s_init_mutex); #endif return s_process_ctx; } // Pull process defaults from environment if (getenv ("ZSYS_IO_THREADS")) s_io_threads = atoi (getenv ("ZSYS_IO_THREADS")); if (getenv ("ZSYS_MAX_SOCKETS")) s_max_sockets = atoi (getenv ("ZSYS_MAX_SOCKETS")); if (getenv ("ZSYS_MAX_MSGSZ")) s_max_msgsz = atoi (getenv ("ZSYS_MAX_MSGSZ")); if (getenv ("ZSYS_ZERO_COPY_RECV")) s_zero_copy_recv = atoi (getenv ("ZSYS_ZERO_COPY_RECV")); if (getenv ("ZSYS_FILE_STABLE_AGE_MSEC")) s_file_stable_age_msec = atoi (getenv ("ZSYS_FILE_STABLE_AGE_MSEC")); if (getenv ("ZSYS_LINGER")) s_linger = atoi (getenv ("ZSYS_LINGER")); if (getenv ("ZSYS_SNDHWM")) s_sndhwm = atoi (getenv ("ZSYS_SNDHWM")); if (getenv ("ZSYS_RCVHWM")) s_rcvhwm = atoi (getenv ("ZSYS_RCVHWM")); if (getenv ("ZSYS_PIPEHWM")) s_pipehwm = atoi (getenv ("ZSYS_PIPEHWM")); if (getenv ("ZSYS_IPV6")) s_ipv6 = atoi (getenv ("ZSYS_IPV6")); if (getenv ("ZSYS_LOGSTREAM")) { if (streq (getenv ("ZSYS_LOGSTREAM"), "stdout")) s_logstream = stdout; else if (streq (getenv ("ZSYS_LOGSTREAM"), "stderr")) s_logstream = stderr; } else s_logstream = stdout; if (getenv ("ZSYS_LOGSYSTEM")) { if (streq (getenv ("ZSYS_LOGSYSTEM"), "true")) s_logsystem = true; else if (streq (getenv ("ZSYS_LOGSYSTEM"), "false")) s_logsystem = false; } if (getenv ("ZSYS_AUTO_USE_FD")) s_auto_use_fd = atoi (getenv ("ZSYS_AUTO_USE_FD")); zsys_catch_interrupts (); #if defined (__WINDOWS__) ZMUTEX_INIT (s_mutex); #endif s_sockref_list = zlist_new (); if (!s_sockref_list) { zsys_shutdown (); #if defined (__UNIX__) ZMUTEX_UNLOCK(s_init_mutex); #endif return NULL; } srandom ((unsigned) time (NULL)); assert (!s_process_ctx); // We use zmq_init/zmq_term to keep compatibility back to ZMQ v2 s_process_ctx = zmq_init ((int) s_io_threads); #if defined (ZMQ_MAX_SOCKETS) zmq_ctx_set (s_process_ctx, ZMQ_MAX_SOCKETS, (int) s_max_sockets); #endif s_initialized = true; #if defined (__UNIX__) atexit (zsys_shutdown); pthread_atfork(NULL, NULL, &zsys_pthread_at_fork_handler); //don't hold the lock because some of the function will call zsys_init again ZMUTEX_UNLOCK(s_init_mutex); #endif // The following functions call zsys_init(), so they MUST be called after // s_initialized is set in order to avoid an infinite recursion if (getenv ("ZSYS_INTERFACE")) zsys_set_interface (getenv ("ZSYS_INTERFACE")); if (getenv ("ZSYS_IPV6_ADDRESS")) zsys_set_ipv6_address (getenv ("ZSYS_IPV6_ADDRESS")); if (getenv ("ZSYS_IPV6_MCAST_ADDRESS")) zsys_set_ipv6_mcast_address (getenv ("ZSYS_IPV6_MCAST_ADDRESS")); else zsys_set_ipv6_mcast_address ("ff02:0:0:0:0:0:0:1"); if (getenv ("ZSYS_IPV4_MCAST_ADDRESS")) zsys_set_ipv4_mcast_address (getenv ("ZSYS_IPV4_MCAST_ADDRESS")); else zsys_set_ipv4_mcast_address (NULL); if (getenv ("ZSYS_LOGIDENT")) zsys_set_logident (getenv ("ZSYS_LOGIDENT")); if (getenv ("ZSYS_LOGSENDER")) zsys_set_logsender (getenv ("ZSYS_LOGSENDER")); zsys_set_max_msgsz (s_max_msgsz); #if defined ZMQ_ZERO_COPY_RECV zmq_ctx_set (s_process_ctx, ZMQ_ZERO_COPY_RECV, s_zero_copy_recv); #endif zsys_set_file_stable_age_msec (s_file_stable_age_msec); if (getenv ("ZSYS_THREAD_PRIORITY")) zsys_set_thread_priority (atoi (getenv ("ZSYS_THREAD_PRIORITY"))); else zsys_set_thread_priority (s_thread_priority); if (getenv ("ZSYS_THREAD_SCHED_POLICY")) zsys_set_thread_sched_policy (atoi (getenv ("ZSYS_THREAD_SCHED_POLICY"))); else zsys_set_thread_sched_policy (s_thread_sched_policy); if (getenv ("ZSYS_THREAD_NAME_PREFIX")) zsys_set_thread_name_prefix (atoi (getenv ("ZSYS_THREAD_NAME_PREFIX"))); else zsys_set_thread_name_prefix (s_thread_name_prefix); if (getenv ("ZSYS_THREAD_NAME_PREFIX_STR")) zsys_set_thread_name_prefix_str (getenv ("ZSYS_THREAD_NAME_PREFIX_STR")); return s_process_ctx; } // atexit or manual termination for the process void zsys_shutdown (void) { if (!s_initialized || s_shutting_down) return; s_shutting_down = true; // The atexit handler is called when the main function exits; // however we may have zactor threads shutting down and still // trying to close their sockets. So if we suspect there are // actors busy (s_open_sockets > 0), then we sleep for a few // hundred milliseconds to allow the actors, if any, to get in // and close their sockets. ZMUTEX_LOCK (s_mutex); size_t busy = s_open_sockets; ZMUTEX_UNLOCK (s_mutex); if (busy) zclock_sleep (200); // No matter, we are now going to shut down // Print the source reference for any sockets the app did not // destroy properly. ZMUTEX_LOCK (s_mutex); s_sockref_t *sockref = (s_sockref_t *) zlist_pop (s_sockref_list); while (sockref) { assert (sockref->filename); zsys_error ("[%d]dangling '%s' socket created at %s:%d", getpid (), zsys_sockname (sockref->type), sockref->filename, (int) sockref->line_nbr); zmq_close (sockref->handle); freen (sockref); sockref = (s_sockref_t *) zlist_pop (s_sockref_list); --s_open_sockets; } zlist_destroy (&s_sockref_list); ZMUTEX_UNLOCK (s_mutex); // Close logsender socket if opened (don't do this in critical section) if (s_logsender) zsock_destroy (&s_logsender); if (s_open_sockets == 0) { zmq_term(s_process_ctx); s_process_ctx = NULL; s_io_threads = 1; s_thread_sched_policy = -1; s_thread_priority = -1; s_thread_name_prefix = -1; strcpy (s_thread_name_prefix_str, "0"); s_max_sockets = 1024; s_max_msgsz = INT_MAX; s_file_stable_age_msec = S_DEFAULT_ZSYS_FILE_STABLE_AGE_MSEC; s_linger = 0; s_sndhwm = 1000; s_rcvhwm = 1000; s_pipehwm = 1000; s_ipv6 = 0; s_auto_use_fd = 0; s_logstream = NULL; s_logsystem = false; } else zsys_error ("dangling sockets: cannot terminate ZMQ safely"); ZMUTEX_DESTROY (s_mutex); // Free dynamically allocated properties freen (s_interface); freen (s_ipv6_address); freen (s_ipv6_mcast_address); freen (s_logident); zsys_interrupted = 0; zctx_interrupted = 0; zsys_handler_reset (); #if defined (__UNIX__) closelog (); // Just to be pedantic #endif s_initialized = false; s_shutting_down = false; } #if defined (__UNIX__) // Restores all CZMQ global state to initial values static void zsys_cleanup (void) { ZMUTEX_LOCK (s_init_mutex); s_process_ctx = NULL; zsys_interrupted = 0; zctx_interrupted = 0; s_first_time = true; handle_signals = true; s_initialized = false; s_io_threads = 1; s_max_sockets = 1024; s_max_msgsz = INT_MAX; s_linger = 0; s_sndhwm = 1000; s_rcvhwm = 1000; s_pipehwm = 1000; s_ipv6 = 0; s_interface = NULL; s_ipv6_address = NULL; s_ipv6_mcast_address = NULL; s_auto_use_fd = 0; s_logident = NULL; s_logstream = NULL; s_logsystem = false; s_logsender = NULL; s_open_sockets = 0; ZMUTEX_UNLOCK (s_init_mutex); } #endif // -------------------------------------------------------------------------- // Get a new ZMQ socket, automagically creating a ZMQ context if this is // the first time. Caller is responsible for destroying the ZMQ socket // before process exits, to avoid a ZMQ deadlock. Note: you should not use // this method in CZMQ apps, use zsock_new() instead. This is for system // use only, really. void * zsys_socket (int type, const char *filename, size_t line_nbr) { // First time initialization; if the application is mixing // its own threading calls with zsock, this may fail if two // threads try to create sockets at the same time. In such // apps, they MUST create a socket in the main program before // starting any threads. If the app uses zactor for its threads // then we can guarantee this to always be safe. zsys_init (); ZMUTEX_LOCK (s_mutex); void *handle = zmq_socket (s_process_ctx, type); if (handle) { // Configure socket with process defaults zsock_set_linger (handle, (int) s_linger); #if (ZMQ_VERSION_MAJOR == 2) // TODO: v2/v3 socket api in zsock_option.inc are not public (not // added to include/zsock.h) so we have to use zmq_setsockopt directly // This should be fixed and zsock_set_hwm should be used instead # if defined (ZMQ_HWM) uint64_t value = s_sndhwm; int rc = zmq_setsockopt (handle, ZMQ_HWM, &value, sizeof (uint64_t)); assert (rc == 0 || zmq_errno () == ETERM); # endif #else // For later versions we use separate SNDHWM and RCVHWM zsock_set_sndhwm (handle, (int) s_sndhwm); zsock_set_rcvhwm (handle, (int) s_rcvhwm); # if defined (ZMQ_IPV6) zsock_set_ipv6 (handle, s_ipv6); # else zsock_set_ipv4only (handle, s_ipv6? 0: 1); # endif #endif // Add socket to reference tracker so we can report leaks; this is // done only when the caller passes a filename/line_nbr if (filename) { s_sockref_t *sockref = (s_sockref_t *) zmalloc (sizeof (s_sockref_t)); if (sockref) { sockref->handle = handle; sockref->type = type; sockref->filename = filename; sockref->line_nbr = line_nbr; zlist_append (s_sockref_list, sockref); } else { zmq_close (handle); ZMUTEX_UNLOCK (s_mutex); return NULL; } } s_open_sockets++; } ZMUTEX_UNLOCK (s_mutex); return handle; } // -------------------------------------------------------------------------- // Destroy/close a ZMQ socket. You should call this for every socket you // create using zsys_socket(). int zsys_close (void *handle, const char *filename, size_t line_nbr) { ZMUTEX_LOCK (s_mutex); // It's possible atexit() has already happened if we're running under // a debugger that redirects the main thread exit. if (s_sockref_list) { s_sockref_t *sockref = (s_sockref_t *) zlist_first (s_sockref_list); while (sockref) { if (sockref->handle == handle) { zlist_remove (s_sockref_list, sockref); freen (sockref); break; } sockref = (s_sockref_t *) zlist_next (s_sockref_list); } } s_open_sockets--; zmq_close (handle); ZMUTEX_UNLOCK (s_mutex); return 0; } // -------------------------------------------------------------------------- // Return ZMQ socket name for socket type char * zsys_sockname (int socktype) { char *type_names [] = { "PAIR", "PUB", "SUB", "REQ", "REP", "DEALER", "ROUTER", "PULL", "PUSH", "XPUB", "XSUB", "STREAM", "SERVER", "CLIENT", "RADIO", "DISH", "SCATTER", "GATHER", "DGRAM" }; // This array matches ZMQ_XXX type definitions assert (ZMQ_PAIR == 0); #if defined (ZMQ_DGRAM) assert (socktype >= 0 && socktype <= ZMQ_DGRAM); #elif defined (ZMQ_SCATTER) assert (socktype >= 0 && socktype <= ZMQ_SCATTER); #elif defined (ZMQ_DISH) assert (socktype >= 0 && socktype <= ZMQ_DISH); #elif defined (ZMQ_CLIENT) assert (socktype >= 0 && socktype <= ZMQ_CLIENT); #elif defined (ZMQ_STREAM) assert (socktype >= 0 && socktype <= ZMQ_STREAM); #else assert (socktype >= 0 && socktype <= ZMQ_XSUB); #endif return type_names [socktype]; } // -------------------------------------------------------------------------- // Create a pipe, which consists of two PAIR sockets connected over inproc. // The pipe is configured to use the zsys_pipehwm setting. Returns the // frontend socket successful, NULL if failed. zsock_t * zsys_create_pipe (zsock_t **backend_p) { zsock_t *frontend = zsock_new (ZMQ_PAIR); zsock_t *backend = zsock_new (ZMQ_PAIR); assert (frontend); assert (backend); #if (ZMQ_VERSION_MAJOR == 2) // TODO: v2/v3 socket api in zsock_option.inc are not public (not // added to include/zsock.h) so we have to use zmq_setsockopt directly // This should be fixed and zsock_set_hwm should be used instead # if defined (ZMQ_HWM) uint64_t value = zsys_pipehwm (); int ret = zmq_setsockopt (zsock_resolve (frontend), ZMQ_HWM, &value, sizeof (uint64_t)); assert (ret == 0 || zmq_errno () == ETERM); value = zsys_pipehwm (); ret = zmq_setsockopt (zsock_resolve (backend), ZMQ_HWM, &value, sizeof (uint64_t)); assert (ret == 0 || zmq_errno () == ETERM); # endif #else zsock_set_sndhwm (frontend, (int) zsys_pipehwm ()); zsock_set_sndhwm (backend, (int) zsys_pipehwm ()); #endif // Now bind and connect pipe ends char endpoint [32]; while (true) { sprintf (endpoint, "inproc://pipe-%04x-%04x", randof (0x10000), randof (0x10000)); if (zsock_bind (frontend, "%s", endpoint) == 0) break; } int rc = zsock_connect (backend, "%s", endpoint); assert (rc != -1); // Connect cannot fail // Return frontend and backend sockets *backend_p = backend; return frontend; } // -------------------------------------------------------------------------- // Set interrupt handler; this saves the default handlers so that a // zsys_handler_reset () can restore them. If you call this multiple times // then the last handler will take affect. If handler_fn is NULL, disables // default SIGINT/SIGTERM handling in CZMQ. void zsys_handler_set (zsys_handler_fn *handler_fn) { if (!handler_fn) { // Disable existing or future signal handling zsys_handler_reset (); handle_signals = false; } else { handle_signals = true; #if defined (__UNIX__) if (s_first_time) { // If first time, save default handlers sigaction (SIGINT, NULL, &sigint_default); sigaction (SIGTERM, NULL, &sigterm_default); s_first_time = false; } // Install signal handler for SIGINT and SIGTERM struct sigaction action; action.sa_handler = handler_fn; action.sa_flags = 0; sigemptyset (&action.sa_mask); sigaction (SIGINT, &action, NULL); sigaction (SIGTERM, &action, NULL); #elif defined (__WINDOWS__) installed_handler_fn = handler_fn; if (s_first_time) { SetConsoleCtrlHandler (s_handler_fn_shim, TRUE); s_first_time = false; } #else # error "No signal handling defined for this platform" #endif } } // -------------------------------------------------------------------------- // Reset interrupt handler, call this at exit if needed // Idempotent; safe to call multiple times void zsys_handler_reset (void) { #if defined (__UNIX__) // Restore default handlers if not already done if (handle_signals && !s_first_time) { sigaction (SIGINT, &sigint_default, NULL); sigaction (SIGTERM, &sigterm_default, NULL); sigint_default.sa_handler = NULL; sigterm_default.sa_handler = NULL; s_first_time = true; } #elif defined (__WINDOWS__) if (handle_signals && !s_first_time) { SetConsoleCtrlHandler (s_handler_fn_shim, FALSE); installed_handler_fn = NULL; s_first_time = true; } #endif } // -------------------------------------------------------------------------- // Set default interrupt handler, so Ctrl-C or SIGTERM will set // zsys_interrupted. Idempotent; safe to call multiple times. void zsys_catch_interrupts (void) { // Catch SIGINT and SIGTERM unless ZSYS_SIGHANDLER=false if ((getenv ("ZSYS_SIGHANDLER") == NULL || strneq (getenv ("ZSYS_SIGHANDLER"), "false")) && handle_signals) zsys_handler_set (s_signal_handler); } // Default internal signal handler static void s_signal_handler (int signal_value) { zctx_interrupted = 1; zsys_interrupted = 1; } // -------------------------------------------------------------------------- // Return true if file exists, else zero bool zsys_file_exists (const char *filename) { assert (filename); return zsys_file_mode (filename) != -1; } // -------------------------------------------------------------------------- // Return size of file, or -1 if not found ssize_t zsys_file_size (const char *filename) { struct stat stat_buf; assert (filename); if (stat ((char *) filename, &stat_buf) == 0) return stat_buf.st_size; else return -1; } // -------------------------------------------------------------------------- // Return file modification time (accounted in seconds usually since // UNIX Epoch, with granularity dependent on underlying filesystem, // and starting point dependent on host OS and maybe its bitness). // Per https://msdn.microsoft.com/en-us/library/w4ddyt9h(vs.71).aspx : // Note In all versions of Microsoft C/C++ except Microsoft C/C++ // version 7.0, and in all versions of Microsoft Visual C++, the time // function returns the current time as the number of seconds elapsed // since midnight on January 1, 1970. In Microsoft C/C++ version 7.0, // time() returned the current time as the number of seconds elapsed // since midnight on December 31, 1899. // This value is "arithmetic" with no big guarantees in the standards, and // normally it should be manipulated with host's datetime suite of routines, // including difftime(), or converted to "struct tm" for any predictable use. // Returns 0 if the file does not exist. time_t zsys_file_modified (const char *filename) { struct stat stat_buf; if (stat (filename, &stat_buf) == 0) return stat_buf.st_mtime; else return 0; } // -------------------------------------------------------------------------- // Return file mode; provides at least support for the POSIX S_ISREG(m) // and S_ISDIR(m) macros and the S_IRUSR and S_IWUSR bits, on all boxes. // Returns a mode_t cast to int, or -1 in case of error. int zsys_file_mode (const char *filename) { #if (defined (__WINDOWS__)) DWORD dwfa = GetFileAttributesA (filename); if (dwfa == 0xffffffff) return -1; dbyte mode = 0; if (dwfa & FILE_ATTRIBUTE_DIRECTORY) mode |= S_IFDIR; else mode |= S_IFREG; if (!(dwfa & FILE_ATTRIBUTE_HIDDEN)) mode |= S_IRUSR; if (!(dwfa & FILE_ATTRIBUTE_READONLY)) mode |= S_IWUSR; return mode; #else struct stat stat_buf; if (stat ((char *) filename, &stat_buf) == 0) return stat_buf.st_mode; else return -1; #endif } // -------------------------------------------------------------------------- // Delete file, return 0 if OK, -1 if not possible. int zsys_file_delete (const char *filename) { assert (filename); #if (defined (__WINDOWS__)) return DeleteFileA (filename)? 0: -1; #else return unlink (filename); #endif } // -------------------------------------------------------------------------- // Check if file is 'stable' // Internal implementation rigged with debugs and called from selftest static bool s_zsys_file_stable (const char *filename, bool verbose) { struct stat stat_buf; if (stat (filename, &stat_buf) == 0) { // File is 'stable' if older (per filesystem stats) than a threshold. // This used to mean more than 1 second old, counted in microseconds // after inflating the st_mtime data - but this way of calculation // has a caveat: if we created the file at Nsec.999msec, or rather // the FS metadata was updated at that point, the st_mtime will be // (after inflation) N.000. So a few milliseconds later, at (N+1)sec, // we find the age difference seems over 1000 so the file is 1 sec // old - even though it has barely been created. Compounding the // issue, some filesystems have worse timestamp precision - e.g. the // FAT filesystem variants are widespread (per SD standards) on // removable media, and only account even seconds in stat data. // Solutions are two-fold: when using stat fields that are precise // to a second (or inpredictably two), we should actually check for // (age > 3000+) in rounded-microsecond accounting. Also, for some // systems we can have `configure`-time checks on presence of more // precise (and less standardized) stat timestamp fields, where we // can presumably avoid rounding to thousands and use (age > 2000). // It might also help to define a zsys_file_modified_msec() whose // actual granularity will be OS-dependent (rounded to 1000 or not). // These are TODO ideas for subsequent work. #if (defined (WIN32)) # define EPOCH_DIFFERENCE 11644473600LL long age = (long) (zclock_time () - EPOCH_DIFFERENCE * 1000 - (stat_buf.st_mtime * 1000)); if (verbose) zsys_debug ("zsys_file_stable@WIN32: file '%s' age is %ld msec " "at timestamp %" PRIi64 " where st_mtime was %jd adjusted by %jd", filename, age, zclock_time (), (intmax_t)(stat_buf.st_mtime * 1000), (intmax_t)(EPOCH_DIFFERENCE * 1000) ); #else long age = (long) (zclock_time () - (stat_buf.st_mtime * 1000)); if (verbose) zsys_debug ("zsys_file_stable@non-WIN32: file '%s' age is %ld msec " "at timestamp %" PRIi64 " where st_mtime was %jd", filename, age, zclock_time (), (intmax_t)(stat_buf.st_mtime * 1000) ); #endif return (age > s_file_stable_age_msec); } else { if (verbose) zsys_debug ("zsys_file_stable: could not stat file '%s'", filename); return false; // File doesn't exist, so not stable } } // Public implementation does not do debugs bool zsys_file_stable (const char *filename) { return s_zsys_file_stable(filename, false); } // -------------------------------------------------------------------------- // Create a file path if it doesn't exist. The file path is treated as a // printf format. int zsys_dir_create (const char *pathname, ...) { va_list argptr; va_start (argptr, pathname); char *formatted = zsys_vprintf (pathname, argptr); va_end (argptr); if (!formatted) return -1; // Create parent directory levels if needed char *slash = strchr (formatted + 1, '/'); while (true) { if (slash) *slash = 0; // Cut at slash int mode = zsys_file_mode (formatted); if (mode == -1) { // Does not exist, try to create it #if (defined (__WINDOWS__)) if (!CreateDirectoryA (formatted, NULL)) { #else if (mkdir (formatted, 0775)) { #endif freen (formatted); return -1; // Failed } } else if ((mode & S_IFDIR) == 0) { // Not a directory, abort } if (!slash) // End if last segment break; *slash = '/'; slash = strchr (slash + 1, '/'); } zstr_free (&formatted); return 0; } // -------------------------------------------------------------------------- // Remove a file path if empty; the pathname is treated as printf format. int zsys_dir_delete (const char *pathname, ...) { va_list argptr; va_start (argptr, pathname); char *formatted = zsys_vprintf (pathname, argptr); va_end (argptr); if (!formatted) return -1; #if (defined (__WINDOWS__)) int rc = RemoveDirectoryA (formatted)? 0: -1; #else int rc = rmdir (formatted); #endif zstr_free (&formatted); return rc; } // -------------------------------------------------------------------------- // Move to a specified working directory. Returns 0 if OK, -1 if this failed. int zsys_dir_change (const char *pathname) { assert (pathname); #if (defined (__UNIX__)) return chdir (pathname); #elif (defined (__WINDOWS__)) return !SetCurrentDirectoryA (pathname); #endif return -1; // Not implemented } // -------------------------------------------------------------------------- // Set private file creation mode; all files created from here will be // readable/writable by the owner only. #if !defined (__WINDOWS__) static mode_t s_old_mask = 0; #endif void zsys_file_mode_private (void) { #if !defined (__WINDOWS__) s_old_mask = umask (S_IWGRP | S_IWOTH | S_IRGRP | S_IROTH); #endif } // -------------------------------------------------------------------------- // Reset default file creation mode; all files created from here will use // process file mode defaults. void zsys_file_mode_default (void) { // Reset process file create mask #if !defined (__WINDOWS__) if (s_old_mask) umask (s_old_mask); #endif } // -------------------------------------------------------------------------- // Return the CZMQ version for run-time API detection; returns version // number into provided fields, providing reference isn't null in each case. void zsys_version (int *major, int *minor, int *patch) { if (major) *major = CZMQ_VERSION_MAJOR; if (minor) *minor = CZMQ_VERSION_MINOR; if (patch) *patch = CZMQ_VERSION_PATCH; } // -------------------------------------------------------------------------- // Format a string using printf formatting, returning a freshly allocated // buffer. If there was insufficient memory, returns NULL. Free the returned // string using zstr_free(). The hinted version allows to optimize by using // a larger starting buffer size (known to/assumed by the developer) and so // avoid reallocations. char * zsys_sprintf_hint (int hint, const char *format, ...) { va_list argptr; va_start (argptr, format); char *string = s_zsys_vprintf_hint (hint, format, argptr); va_end (argptr); return (string); } // -------------------------------------------------------------------------- // Format a string using printf formatting, returning a freshly allocated // buffer. If there was insufficient memory, returns NULL. Free the returned // string using zstr_free(). char * zsys_sprintf (const char *format, ...) { // Effectively this is a copy of the small zsys_sprintf_hint with // hardcoded hint value; this is is cheaper than parsing va_list // several times to just call the other implementation cleanly. va_list argptr; va_start (argptr, format); char *string = s_zsys_vprintf_hint (256, format, argptr); va_end (argptr); return (string); } // -------------------------------------------------------------------------- // Format a string with variable arguments, returning a freshly allocated // buffer. If there was insufficient memory, returns NULL. Free the returned // string using zstr_free(). static inline char * s_zsys_vprintf_hint (int hint, const char *format, va_list argptr) { if (hint <= 0) { // The hint is not a hard requrement so no error here. // Just fall back to legacy default. hint = 256; } // Must use int "size" to compare to "required" (from vsnprintf) int size = hint; char *string = (char *) malloc (size); if (!string) return NULL; // Using argptr is destructive, so we take a copy each time we need it // We define va_copy for Windows in czmq_prelude.h va_list my_argptr; va_copy (my_argptr, argptr); int required = vsnprintf (string, size, format, my_argptr); va_end (my_argptr); #ifdef __WINDOWS__ if (required < 0 || required >= size) { va_copy (my_argptr, argptr); #ifdef _MSC_VER required = _vscprintf (format, argptr); #else required = vsnprintf (NULL, 0, format, argptr); #endif va_end (my_argptr); } #endif if (required < 0) { // vsnprintf failed at unknown point; at least prohibit // accesses by string consumers into random memory string [hint - 1] = '\0'; return string; } // If formatted string cannot fit into small string, reallocate a // larger buffer for it. If it did fit, we only called vsnprintf() // once and already have the good result, so pre-counting with a // NULL string would not be beneficial for shorter texts (always // calling vsnprintf() twice then). if (required >= hint) { size = required + 1; freen (string); string = (char *) malloc (size); if (string) { va_copy (my_argptr, argptr); vsnprintf (string, size, format, my_argptr); va_end (my_argptr); } } return string; } char * zsys_vprintf (const char *format, va_list argptr) { return (s_zsys_vprintf_hint(256, format, argptr)); } // -------------------------------------------------------------------------- // Create a UDP beacon socket; if the routable option is true, uses // multicast (not yet implemented), else uses broadcast. This method // and related ones might _eventually_ be moved to a zudp class. SOCKET zsys_udp_new (bool routable) { //IPV6 Multicast not implemented yet so only allow routable if IPv4 assert ((routable && !zsys_ipv6 ()) || !routable); SOCKET udpsock; int type = SOCK_DGRAM; #ifdef CZMQ_HAVE_SOCK_CLOEXEC // Ensure socket is closed by exec() functions. type |= SOCK_CLOEXEC; #endif if (zsys_ipv6 ()) udpsock = socket (AF_INET6, type, IPPROTO_UDP); else udpsock = socket (AF_INET, type, IPPROTO_UDP); if (udpsock == INVALID_SOCKET) { zsys_socket_error ("socket"); return INVALID_SOCKET; } // On Windows, preventing sockets to be inherited by child processes. #if defined (__WINDOWS__) && defined (HANDLE_FLAG_INHERIT) if (!SetHandleInformation ((HANDLE) udpsock, HANDLE_FLAG_INHERIT, 0)) zsys_socket_error ("SetHandleInformation (HANDLE_FLAG_INHERIT)"); #endif // Ask operating system for broadcast permissions on socket int on = 1; if (setsockopt (udpsock, SOL_SOCKET, SO_BROADCAST, (char *) &on, sizeof (on)) == SOCKET_ERROR) zsys_socket_error ("setsockopt (SO_BROADCAST)"); // Allow multiple owners to bind to socket; incoming // messages will replicate to each owner if (setsockopt (udpsock, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof (on)) == SOCKET_ERROR) zsys_socket_error ("setsockopt (SO_REUSEADDR)"); #if defined (SO_REUSEPORT) // On some platforms we have to ask to reuse the port if (setsockopt (udpsock, SOL_SOCKET, SO_REUSEPORT, (char *) &on, sizeof (on)) == SOCKET_ERROR) zsys_socket_error ("setsockopt (SO_REUSEPORT)"); #endif // Only set TLL for IPv4 if (routable && zsys_mcast_ttl () > 1) { int ttl = zsys_mcast_ttl (); if (setsockopt (udpsock, IPPROTO_IP, IP_MULTICAST_TTL, (const char *) &ttl, sizeof (ttl)) == SOCKET_ERROR) zsys_socket_error ("setsockopt (IP_MULTICAST_TTL)"); } // TODO // Set TLL for IPv6 /*if (routable && zsys_ipv6 ()) { int ttl = zsys_mcast_ttl (); if (setsockopt (udpsock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (char *) &ttl, sizeof (ttl)) == SOCKET_ERROR) zsys_socket_error ("setsockopt (IP_MULTICAST_TTL)"); }*/ return udpsock; } // -------------------------------------------------------------------------- // Close a UDP socket int zsys_udp_close (SOCKET handle) { #if (defined (__WINDOWS__)) return closesocket (handle); #else return close (handle); #endif } // -------------------------------------------------------------------------- // Send zframe to UDP socket, return -1 if sending failed due to // interface having disappeared (happens easily with WiFi) int zsys_udp_send (SOCKET udpsock, zframe_t *frame, inaddr_t *address, int addrlen) { assert (frame); assert (address); if (sendto (udpsock, (char *) zframe_data (frame), (int) zframe_size (frame), 0, // Flags (struct sockaddr *) address, addrlen) == -1) { zsys_debug ("zsys_udp_send: failed, reason=%s", strerror (errno)); return -1; // UDP broadcast not possible } else return 0; } // -------------------------------------------------------------------------- // Receive zframe from UDP socket, and set address of peer that sent it // The peername must be a char [INET_ADDRSTRLEN] array if IPv6 is disabled or // NI_MAXHOST if it's enabled. Returns NULL when failing to get peer address. zframe_t * zsys_udp_recv (SOCKET udpsock, char *peername, int peerlen) { char buffer [UDP_FRAME_MAX]; in6addr_t address6; socklen_t address_len = sizeof (in6addr_t); ssize_t size = recvfrom ( udpsock, buffer, UDP_FRAME_MAX, 0, // Flags (struct sockaddr *) &address6, &address_len); if (size == SOCKET_ERROR) zsys_socket_error ("recvfrom"); // Get sender address as printable string int rc = getnameinfo ((struct sockaddr *) &address6, address_len, peername, peerlen, NULL, 0, NI_NUMERICHOST); if (rc) { zsys_warning ("zsys_udp_recv: getnameinfo failed, reason=%s", gai_strerror (rc)); return NULL; } // Some platform's getnameinfo, like Solaris, appear not to append the // interface name when parsing a link-local IPv6 address. These addresses // cannot be used without the interface, so we must append it manually. // On Windows, if_indextoname is only available from Vista. #if !defined (__WINDOWS__) || (_WIN32_WINNT >= 0x0600) if (address6.sin6_family == AF_INET6 && IN6_IS_ADDR_LINKLOCAL (&address6.sin6_addr) && !strchr (peername, '%')) { char ifname [IF_NAMESIZE] = {0}; if_indextoname (address6.sin6_scope_id, ifname); strcat (peername, "%"); strcat (peername, ifname); } #endif return zframe_new (buffer, size); } // -------------------------------------------------------------------------- // Handle an I/O error on some socket operation; will report and die on // fatal errors, and continue silently on "try again" errors. void zsys_socket_error (const char *reason) { bool check_errno; #if defined (__WINDOWS__) switch (WSAGetLastError ()) { case WSAEINTR: errno = EINTR; break; case WSAEBADF: errno = EBADF; break; case WSAEWOULDBLOCK: errno = EAGAIN; break; case WSAEINPROGRESS: errno = EAGAIN; break; case WSAENETDOWN: errno = ENETDOWN; break; case WSAECONNRESET: errno = ECONNRESET; break; case WSAECONNABORTED: errno = EPIPE; break; case WSAESHUTDOWN: errno = ECONNRESET; break; case WSAEINVAL: errno = EPIPE; break; case WSAEADDRNOTAVAIL: errno = EADDRNOTAVAIL; break; case WSAEADDRINUSE: errno = EADDRINUSE; break; default: errno = GetLastError (); } #endif check_errno = (errno == EAGAIN || errno == ENETDOWN || errno == EHOSTUNREACH || errno == ENETUNREACH || errno == EINTR || errno == EPIPE || errno == ECONNRESET); #if defined (ENOPROTOOPT) check_errno = (check_errno || errno == ENOPROTOOPT); #endif #if defined (EHOSTDOWN) check_errno = (check_errno || errno == EHOSTDOWN); #endif #if defined (EOPNOTSUPP) check_errno = (check_errno || errno == EOPNOTSUPP); #endif #if defined (EWOULDBLOCK) check_errno = (check_errno || errno == EWOULDBLOCK); #endif #if defined (EPROTO) check_errno = (check_errno || errno == EPROTO); #endif #if defined (ENONET) check_errno = (check_errno || errno == ENONET); #endif if (check_errno) return; // Ignore error and try again else { zsys_error ("(UDP) error '%s' on %s", strerror (errno), reason); assert (false); } } // -------------------------------------------------------------------------- // Return current host name, for use in public tcp:// endpoints. Caller gets // a freshly allocated string, should free it using zstr_free(). If the host // name is not resolvable, returns NULL. char * zsys_hostname (void) { char hostname [NI_MAXHOST]; gethostname (hostname, NI_MAXHOST); hostname [NI_MAXHOST - 1] = 0; struct hostent *host = gethostbyname (hostname); if (host && host->h_name) return strdup (host->h_name); else return NULL; } // -------------------------------------------------------------------------- // Move the current process into the background. The precise effect depends // on the operating system. On POSIX boxes, moves to a specified working // directory (if specified), closes all file handles, reopens stdin, stdout, // and stderr to the null device, and sets the process to ignore SIGHUP. On // Windows, does nothing. Returns 0 if OK, -1 if there was an error. int zsys_daemonize (const char *workdir) { #if (defined (__UNIX__)) // Defines umask for new files this process will create mode_t file_mask = 027; // Complement of 0750 // Recreate our process as a child of init int fork_result = fork (); if (fork_result < 0) // < 0 is an error return -1; // Could not fork else if (fork_result > 0) // > 0 is the parent process exit (0); // End parent process // Move to a safe and known directory, which is supplied as an // argument to this function (or not, if workdir is NULL or empty). if (workdir && zsys_dir_change (workdir)) { zsys_error ("cannot chdir to '%s'", workdir); return -1; } // Close all open file descriptors inherited from the parent // process, to reduce the resources we use int file_handle = sysconf (_SC_OPEN_MAX); while (file_handle) close (file_handle--); // Ignore any errors // Set the umask for new files we might create umask (file_mask); // Set standard input and output to the null device so that any // code that assumes that these files are open will work file_handle = open ("/dev/null", O_RDWR); int fh_stdout = dup (file_handle); int fh_stderr = dup (file_handle); assert (fh_stdout); assert (fh_stderr); // Ignore any hangup signal from the controlling console signal (SIGHUP, SIG_IGN); #endif return 0; } // -------------------------------------------------------------------------- // Drop the process ID into the lockfile, with exclusive lock, and switch // the process to the specified group and/or user. Any of the arguments // may be null, indicating a no-op. Returns 0 on success, -1 on failure. // Note if you combine this with zsys_daemonize, run after, not before // that method, or the lockfile will hold the wrong process ID. int zsys_run_as (const char *lockfile, const char *group, const char *user) { #if (defined (__UNIX__)) // Switch to effective user ID (who owns executable); for // system services this should be root, so that we can write // the PID file into e.g. /var/run/ if (seteuid (geteuid ())) { zsys_error ("cannot set effective user id: %s", strerror (errno)); return -1; } if (lockfile) { // We enforce a lock on the lockfile, if specified, so that // only one copy of the process can run at once. int handle = open (lockfile, O_RDWR | O_CREAT, 0640); if (handle < 0) { zsys_error ("cannot open lockfile '%s': %s", lockfile, strerror (errno)); return -1; } else { struct flock filelock; filelock.l_type = F_WRLCK; // F_RDLCK, F_WRLCK, F_UNLCK filelock.l_whence = SEEK_SET; // SEEK_SET, SEEK_CUR, SEEK_END filelock.l_start = 0; // Offset from l_whence filelock.l_len = 0; // length, 0 = to EOF filelock.l_pid = getpid (); if (fcntl (handle, F_SETLK, &filelock)) { zsys_error ("cannot get lock: %s", strerror (errno)); return -1; } } // We record the current process id in the lock file char pid_buffer [32]; snprintf (pid_buffer, sizeof (pid_buffer), "%6" PRIi64 "\n", (int64_t)getpid ()); if ((size_t) write (handle, pid_buffer, strlen (pid_buffer)) != strlen (pid_buffer)) { zsys_error ("cannot write to lockfile: %s", strerror (errno)); close (handle); return -1; } } if (group) { zsys_info ("running under group '%s'", group); struct group *grpbuf = NULL; grpbuf = getgrnam (group); if (grpbuf == NULL || setgid (grpbuf->gr_gid)) { zsys_error ("could not switch group: %s", strerror (errno)); return -1; } } if (user) { zsys_info ("running under user '%s'", user); struct passwd *pwdbuf = NULL; pwdbuf = getpwnam (user); if (pwdbuf == NULL || setuid (pwdbuf->pw_uid)) { zsys_error ("could not switch user: %s", strerror (errno)); return -1; } } else { // Switch back to real user ID (who started process) if (setuid (getuid ())) { zsys_error ("cannot set real user id: %s", strerror (errno)); return -1; } } return 0; #else // This is not yet ported to Windows and should not succeed there. return -1; #endif } // -------------------------------------------------------------------------- // Returns true if the underlying libzmq supports CURVE security. // Uses a heuristic probe according to the version of libzmq being used. bool zsys_has_curve (void) { #if defined (ZMQ_CURVE_SERVER) # if defined (ZMQ_HAS_CAPABILITIES) // This is the most modern way of probing libzmq capabilities return zmq_has ("curve") != 0; # else // However trying the zmq_setsockopt will also work int rc = -1; // assume we fail void *ctx = zmq_ctx_new (); if (ctx) { void *pub = zmq_socket (ctx, ZMQ_PUB); if (pub) { int as_server = 1; rc = zmq_setsockopt (pub, ZMQ_CURVE_SERVER, &as_server, sizeof (int)); zmq_close (pub); } zmq_term (ctx); } return rc != -1; # endif #else return false; #endif } // -------------------------------------------------------------------------- // Configure the number of I/O threads that ZeroMQ will use. A good // rule of thumb is one thread per gigabit of traffic in or out. The // default is 1, sufficient for most applications. If the environment // variable ZSYS_IO_THREADS is defined, that provides the default. // Note that this method is valid only before any socket is created. void zsys_set_io_threads (size_t io_threads) { zsys_init (); ZMUTEX_LOCK (s_mutex); if (s_open_sockets) zsys_error ("zsys_io_threads() is not valid after creating sockets"); assert (s_open_sockets == 0); s_io_threads = io_threads; #if ZMQ_VERSION < ZMQ_MAKE_VERSION(3, 2, 0) zmq_term (s_process_ctx); s_process_ctx = zmq_init ((int) s_io_threads); #else # if defined (ZMQ_IO_THREADS) zmq_ctx_set (s_process_ctx, ZMQ_IO_THREADS, s_io_threads); # endif #endif ZMUTEX_UNLOCK (s_mutex); #if ZMQ_VERSION < ZMQ_MAKE_VERSION(3, 2, 0) // Reinitialised outside of the lock to avoid recursive lock zsys_set_max_msgsz (s_max_msgsz); zsys_set_max_sockets (s_max_sockets); #endif } // -------------------------------------------------------------------------- // Configure the scheduling policy of the ZMQ context thread pool. // Not available on Windows. See the sched_setscheduler man page or sched.h // for more information. If the environment variable ZSYS_THREAD_SCHED_POLICY // is defined, that provides the default. // Note that this method is valid only before any socket is created. void zsys_set_thread_sched_policy (int policy) { if (policy < 0) return; zsys_init (); ZMUTEX_LOCK (s_mutex); // If the app is misusing this method, burn it with fire if (s_open_sockets) zsys_error ("zsys_set_thread_sched_policy() is not valid after" " creating sockets"); assert (s_open_sockets == 0); s_thread_sched_policy = policy; #if defined (ZMQ_THREAD_SCHED_POLICY) zmq_ctx_set (s_process_ctx, ZMQ_THREAD_SCHED_POLICY, s_thread_sched_policy); #endif ZMUTEX_UNLOCK (s_mutex); } // -------------------------------------------------------------------------- // Configure the numeric prefix to each thread created for the internal // context's thread pool. This option is only supported on Linux. // If the environment variable ZSYS_THREAD_NAME_PREFIX is defined, that // provides the default. // Note that this method is valid only before any socket is created. void zsys_set_thread_name_prefix (int prefix) { if (prefix < 0) return; zsys_init (); ZMUTEX_LOCK (s_mutex); // If the app is misusing this method, burn it with fire if (s_open_sockets) zsys_error ("zsys_set_thread_name_prefix() is not valid after" " creating sockets"); assert (s_open_sockets == 0); s_thread_name_prefix = prefix; #if defined (ZMQ_THREAD_NAME_PREFIX) zmq_ctx_set (s_process_ctx, ZMQ_THREAD_NAME_PREFIX, s_thread_name_prefix); #endif ZMUTEX_UNLOCK (s_mutex); } // -------------------------------------------------------------------------- // Return ZMQ_THREAD_NAME_PREFIX option. int zsys_thread_name_prefix () { zsys_init (); ZMUTEX_LOCK (s_mutex); #if defined (ZMQ_THREAD_NAME_PREFIX) s_thread_name_prefix = zmq_ctx_get (s_process_ctx, ZMQ_THREAD_NAME_PREFIX); #endif ZMUTEX_UNLOCK (s_mutex); return s_thread_name_prefix; } // -------------------------------------------------------------------------- // Configure the string prefix to each thread created for the internal // context's thread pool. This option is only supported on Linux. // If the environment variable ZSYS_THREAD_NAME_PREFIX_STR is defined, that // provides the default. // Note that this method is valid only before any socket is created. void zsys_set_thread_name_prefix_str (const char *prefix) { size_t prefix_len = 0; if (!prefix) return; prefix_len = strlen (prefix); if (prefix_len == 0 || prefix_len > sizeof (s_thread_name_prefix_str) - 1) return; zsys_init (); ZMUTEX_LOCK (s_mutex); // If the app is misusing this method, burn it with fire if (s_open_sockets) zsys_error ("zsys_set_thread_name_prefix() is not valid after" " creating sockets"); assert (s_open_sockets == 0); strcpy(s_thread_name_prefix_str, prefix); #if defined (ZMQ_THREAD_NAME_PREFIX) && defined (ZMQ_BUILD_DRAFT_API) && \ ((ZMQ_VERSION_MAJOR > 4) || \ ((ZMQ_VERSION_MAJOR >= 4) && ((ZMQ_VERSION_MINOR > 3) || \ ((ZMQ_VERSION_MINOR >= 3) && (ZMQ_VERSION_PATCH >= 3))))) zmq_ctx_set_ext (s_process_ctx, ZMQ_THREAD_NAME_PREFIX, s_thread_name_prefix_str, sizeof (s_thread_name_prefix_str)); #endif ZMUTEX_UNLOCK (s_mutex); } // -------------------------------------------------------------------------- // Return ZMQ_THREAD_NAME_PREFIX_STR option. const char * zsys_thread_name_prefix_str () { size_t prefix_len = sizeof (s_thread_name_prefix_str); zsys_init (); ZMUTEX_LOCK (s_mutex); #if defined (ZMQ_THREAD_NAME_PREFIX) && defined (ZMQ_BUILD_DRAFT_API) && \ ((ZMQ_VERSION_MAJOR > 4) || \ ((ZMQ_VERSION_MAJOR >= 4) && ((ZMQ_VERSION_MINOR > 3) || \ ((ZMQ_VERSION_MINOR >= 3) && (ZMQ_VERSION_PATCH >= 3))))) zmq_ctx_get_ext (s_process_ctx, ZMQ_THREAD_NAME_PREFIX, s_thread_name_prefix_str, &prefix_len); #else (void) prefix_len; #endif ZMUTEX_UNLOCK (s_mutex); return s_thread_name_prefix_str; } // -------------------------------------------------------------------------- // Adds a specific CPU to the affinity list of the ZMQ context thread pool. // This option is only supported on Linux. // Note that this method is valid only before any socket is created. void zsys_thread_affinity_cpu_add (int cpu) { if (cpu < 0) return; zsys_init (); ZMUTEX_LOCK (s_mutex); // If the app is misusing this method, burn it with fire if (s_open_sockets) zsys_error ("zsys_set_thread_sched_policy() is not valid after" " creating sockets"); assert (s_open_sockets == 0); #if defined (ZMQ_THREAD_AFFINITY_CPU_ADD) zmq_ctx_set (s_process_ctx, ZMQ_THREAD_AFFINITY_CPU_ADD, cpu); #endif ZMUTEX_UNLOCK (s_mutex); } // -------------------------------------------------------------------------- // Removes a specific CPU to the affinity list of the ZMQ context thread pool. // This option is only supported on Linux. // Note that this method is valid only before any socket is created. void zsys_thread_affinity_cpu_remove (int cpu) { if (cpu < 0) return; zsys_init (); ZMUTEX_LOCK (s_mutex); // If the app is misusing this method, burn it with fire if (s_open_sockets) zsys_error ("zsys_set_thread_sched_policy() is not valid after" " creating sockets"); assert (s_open_sockets == 0); #if defined (ZMQ_THREAD_AFFINITY_CPU_REMOVE) zmq_ctx_set (s_process_ctx, ZMQ_THREAD_AFFINITY_CPU_REMOVE, cpu); #endif ZMUTEX_UNLOCK (s_mutex); } // -------------------------------------------------------------------------- // Configure the scheduling priority of the ZMQ context thread pool. // Not available on Windows. See the sched_setscheduler man page or sched.h // for more information. If the environment variable ZSYS_THREAD_PRIORITY is // defined, that provides the default. // Note that this method is valid only before any socket is created. void zsys_set_thread_priority (int priority) { if (priority < 0) return; zsys_init (); ZMUTEX_LOCK (s_mutex); // If the app is misusing this method, burn it with fire if (s_open_sockets) zsys_error ("zsys_set_thread_priority() is not valid after" " creating sockets"); assert (s_open_sockets == 0); s_thread_priority = priority; #if defined (ZMQ_THREAD_PRIORITY) zmq_ctx_set (s_process_ctx, ZMQ_THREAD_PRIORITY, s_thread_priority); #endif ZMUTEX_UNLOCK (s_mutex); } // -------------------------------------------------------------------------- // Configure the number of sockets that ZeroMQ will allow. The default // is 1024. The actual limit depends on the system, and you can query it // by using zsys_socket_limit (). A value of zero means "maximum". // Note that this method is valid only before any socket is created. void zsys_set_max_sockets (size_t max_sockets) { zsys_init (); ZMUTEX_LOCK (s_mutex); // If the app is misusing this method, burn it with fire if (s_open_sockets) zsys_error ("zsys_max_sockets() is not valid after creating sockets"); assert (s_open_sockets == 0); s_max_sockets = max_sockets? max_sockets: zsys_socket_limit (); #if defined (ZMQ_MAX_SOCKETS) zmq_ctx_set (s_process_ctx, ZMQ_MAX_SOCKETS, (int) s_max_sockets); #endif ZMUTEX_UNLOCK (s_mutex); } // -------------------------------------------------------------------------- // Return maximum number of ZeroMQ sockets that the system will support. size_t zsys_socket_limit (void) { size_t socket_limit; #if defined (ZMQ_SOCKET_LIMIT) if (s_process_ctx) socket_limit = (size_t) zmq_ctx_get (s_process_ctx, ZMQ_SOCKET_LIMIT); else { void *ctx = zmq_init (1); socket_limit = (size_t) zmq_ctx_get (ctx, ZMQ_SOCKET_LIMIT); zmq_term (ctx); } // ZeroMQ used to report a nonsense value (2^31) which if used would // on zmq_ctx_set (ZMQ_MAX_SOCKETS) cause an out-of-memory error. So // if we're running on an older library, enforce a sane limit. if (socket_limit > 65535) socket_limit = 65535; #else socket_limit = 1024; #endif return socket_limit; } // -------------------------------------------------------------------------- // Configure the maximum allowed size of a message sent. // The default is INT_MAX. void zsys_set_max_msgsz (int max_msgsz) { if (max_msgsz < 0) return; zsys_init (); ZMUTEX_LOCK (s_mutex); s_max_msgsz = max_msgsz; #if defined (ZMQ_MAX_MSGSZ) zmq_ctx_set (s_process_ctx, ZMQ_MAX_MSGSZ, (int) s_max_msgsz); #endif ZMUTEX_UNLOCK (s_mutex); } // -------------------------------------------------------------------------- // Configure whether to use zero copy strategy in libzmq. If the environment // variable ZSYS_ZERO_COPY_RECV is defined, that provides the default. // Otherwise the default is 1. void zsys_set_zero_copy_recv(int zero_copy) { zsys_init (); ZMUTEX_LOCK (s_mutex); s_zero_copy_recv = zero_copy; #if defined (ZMQ_ZERO_COPY_RECV) zmq_ctx_set (s_process_ctx, ZMQ_ZERO_COPY_RECV, s_zero_copy_recv); #endif ZMUTEX_UNLOCK (s_mutex); } // -------------------------------------------------------------------------- // Return ZMQ_ZERO_COPY_RECV option. int zsys_zero_copy_recv() { zsys_init (); ZMUTEX_LOCK (s_mutex); #if defined (ZMQ_ZERO_COPY_RECV) s_zero_copy_recv = zmq_ctx_get (s_process_ctx, ZMQ_ZERO_COPY_RECV); #endif ZMUTEX_UNLOCK (s_mutex); return s_zero_copy_recv; } // -------------------------------------------------------------------------- // Return maximum message size. int zsys_max_msgsz (void) { zsys_init (); ZMUTEX_LOCK (s_mutex); #if defined (ZMQ_MAX_MSGSZ) s_max_msgsz = zmq_ctx_get (s_process_ctx, ZMQ_MAX_MSGSZ); #endif ZMUTEX_UNLOCK (s_mutex); return s_max_msgsz; } // -------------------------------------------------------------------------- // *** Draft method, for development use, may change without warning *** // Check if default interrupt handler of Ctrl-C or SIGTERM was called. // Does not work if ZSYS_SIGHANDLER is false and code does not call // set interrupted on signal. bool zsys_is_interrupted (void) { return zsys_interrupted != 0; } // -------------------------------------------------------------------------- // *** Draft method, for development use, may change without warning *** // Set interrupted flag. This is done by default signal handler, however // this can be handy for language bindings or cases without default // signal handler. void zsys_set_interrupted (void) { zctx_interrupted = 1; zsys_interrupted = 1; } // -------------------------------------------------------------------------- // Configure the threshold value of filesystem object age per st_mtime // that should elapse until we consider that object "stable" at the // current zclock_time() moment. // The default is S_DEFAULT_ZSYS_FILE_STABLE_AGE_MSEC defined in zsys.c // which generally depends on host OS, with fallback value of 5000. void zsys_set_file_stable_age_msec (int64_t file_stable_age_msec) { if (file_stable_age_msec < 1) return; zsys_init (); ZMUTEX_LOCK (s_mutex); s_file_stable_age_msec = file_stable_age_msec; ZMUTEX_UNLOCK (s_mutex); } // -------------------------------------------------------------------------- // Return current threshold value of file stable age in msec. // This can be used in code that chooses to wait for this timeout // before testing if a filesystem object is "stable" or not. // Note that the OS timer quantization can bite you, so it may be // reasonably safe to sleep/wait/poll for a larger timeout before // assuming a fault, e.g. the default timer resolution on Windows // is 15.6 ms (per timer interrupt 64 times a second), graphed here: // https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx // and Unix/Linux OSes also have different-resolution timers. int64_t zsys_file_stable_age_msec (void) { zsys_init (); return s_file_stable_age_msec; } // -------------------------------------------------------------------------- // Configure the default linger timeout in msecs for new zsock instances. // You can also set this separately on each zsock_t instance. The default // linger time is zero, i.e. any pending messages will be dropped. If the // environment variable ZSYS_LINGER is defined, that provides the default. // Note that process exit will typically be delayed by the linger time. void zsys_set_linger (size_t linger) { zsys_init (); ZMUTEX_LOCK (s_mutex); s_linger = linger; ZMUTEX_UNLOCK (s_mutex); } // -------------------------------------------------------------------------- // Configure the default outgoing pipe limit (HWM) for new zsock instances. // You can also set this separately on each zsock_t instance. The default // HWM is 1,000, on all versions of ZeroMQ. If the environment variable // ZSYS_SNDHWM is defined, that provides the default. Note that a value of // zero means no limit, i.e. infinite memory consumption. void zsys_set_sndhwm (size_t sndhwm) { zsys_init (); ZMUTEX_LOCK (s_mutex); s_sndhwm = sndhwm; ZMUTEX_UNLOCK (s_mutex); } // -------------------------------------------------------------------------- // Configure the default incoming pipe limit (HWM) for new zsock instances. // You can also set this separately on each zsock_t instance. The default // HWM is 1,000, on all versions of ZeroMQ. If the environment variable // ZSYS_RCVHWM is defined, that provides the default. Note that a value of // zero means no limit, i.e. infinite memory consumption. void zsys_set_rcvhwm (size_t rcvhwm) { zsys_init (); ZMUTEX_LOCK (s_mutex); s_rcvhwm = rcvhwm; ZMUTEX_UNLOCK (s_mutex); } // -------------------------------------------------------------------------- // Configure the default HWM for zactor internal pipes; this is set on both // ends of the pipe, for outgoing messages only (sndhwm). The default HWM is // 1,000, on all versions of ZeroMQ. If the environment var ZSYS_ACTORHWM is // defined, that provides the default. Note that a value of zero means no // limit, i.e. infinite memory consumption. void zsys_set_pipehwm (size_t pipehwm) { zsys_init (); ZMUTEX_LOCK (s_mutex); s_pipehwm = pipehwm; ZMUTEX_UNLOCK (s_mutex); } // -------------------------------------------------------------------------- // Return the HWM for zactor internal pipes. size_t zsys_pipehwm (void) { return s_pipehwm; } // -------------------------------------------------------------------------- // Configure use of IPv6 for new zsock instances. By default sockets accept // and make only IPv4 connections. When you enable IPv6, sockets will accept // and connect to both IPv4 and IPv6 peers. You can override the setting on // each zsock_t instance. The default is IPv4 only (ipv6 set to 0). If the // environment variable ZSYS_IPV6 is defined (as 1 or 0), this provides the // default. Note: has no effect on ZMQ v2. void zsys_set_ipv6 (int ipv6) { zsys_init (); ZMUTEX_LOCK (s_mutex); s_ipv6 = ipv6; ZMUTEX_UNLOCK (s_mutex); } // -------------------------------------------------------------------------- // Return use of IPv6 for zsock instances. int zsys_ipv6 (void) { return s_ipv6; } // Test if ipv6 is available on the system. The only way to reliably // check is to actually open a socket and try to bind it. (ported from // libzmq) bool zsys_ipv6_available (void) { #if defined(__WINDOWS__) && (_WIN32_WINNT < 0x0600) return 0; #else int rc, ipv6 = 1; struct sockaddr_in6 test_addr; memset (&test_addr, 0, sizeof (test_addr)); test_addr.sin6_family = AF_INET6; inet_pton (AF_INET6, "::1", &(test_addr.sin6_addr)); SOCKET fd = socket (AF_INET6, SOCK_STREAM, IPPROTO_IP); if (fd == INVALID_SOCKET) ipv6 = 0; else { #if defined(__WINDOWS__) setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (const char *) &ipv6, sizeof (int)); rc = setsockopt (fd, IPPROTO_IPV6, IPV6_V6ONLY, (const char *) &ipv6, sizeof (int)); if (rc == SOCKET_ERROR) ipv6 = 0; else { rc = bind (fd, (struct sockaddr *) &test_addr, sizeof (test_addr)); if (rc == SOCKET_ERROR) ipv6 = 0; } closesocket (fd); #else setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &ipv6, sizeof (int)); rc = setsockopt (fd, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6, sizeof (int)); if (rc != 0) ipv6 = 0; else { rc = bind (fd, (struct sockaddr *) (&test_addr), sizeof (test_addr)); if (rc != 0) ipv6 = 0; } close (fd); #endif } return ipv6; #endif // _WIN32_WINNT < 0x0600 } // -------------------------------------------------------------------------- // Set network interface name to use for broadcasts, particularly zbeacon. // This lets the interface be configured for test environments where required.
{'commit': 'diff --git a/src/zsys.c b/src/zsys.c\nindex 046fd504..296f5d1a 100644\n--- a/src/zsys.c\n+++ b/src/zsys.c\n@@ -123,7 +123,8 @@ zsys_handler_reset (void)\n \n //  NOT thread safe, not a good design; this is to test the feasibility\n //  of forcing a network interface name instead of writing code to find it.\n-static char *s_interface = NULL;\n+static char *\n+    s_interface = NULL;\n \n void\n zsys_set_interface (const char *interface_name)\n@@ -135,20 +136,21 @@ zsys_set_interface (const char *interface_name)\n \n //  Return network interface name to use for broadcasts.\n //  Returns "" if no interface was set.\n-//  This is experimental; may be merged into zbeacon class.\n \n char *\n zsys_interface (void)\n {\n-    if (s_interface) return s_interface;\n-\n-    // if the environment variable ZSYS_INTERFACE is set, use that as the\n-    // default interface name. This lets the environment variable be configured\n-    // for test environments where required. For example, on Mac OS X, zbeacon\n-    // cannot bind to 255.255.255.255 which is the default when there is no\n-    // specified interface.\n-    char* env = getenv ("ZSYS_INTERFACE");\n-    if (env) return env;\n+    if (s_interface)\n+        return s_interface;\n+\n+    //  If the environment variable ZSYS_INTERFACE is set, use that as\n+    //  the default interface name. This lets the environment variable\n+    //  be configured for test environments where required. For example,\n+    //  on Mac OS X, zbeacon cannot bind to 255.255.255.255 which is the\n+    //  default when there is no specified interface.\n+    char *env = getenv ("ZSYS_INTERFACE");\n+    if (env)\n+        return env;\n     return "";\n }\n \n', 'commit_subject': 'Cosmetic fixes', 'repo_name': 'zeromq/czmq', 'committer': {'name': 'Pieter Hintjens', 'email': '8df74dda184c19df19d4755602108540e071bf38@imatix.com', 'time_sec': '1395323277', 'tz_offset': '120', 'date': {'seconds': '1395323277'}}, 'commit_tree': '45da3f46da9b8c84e679aa128d7fcbcef24c392c', 'commit_message': 'Cosmetic fixes\n', 'language_name': 'Shell', 'license': 'mpl-2.0', 'hunks': '@@ -123,7 +123,8 @@ zsys_handler_reset (void)\n \n //  NOT thread safe, not a good design; this is to test the feasibility\n //  of forcing a network interface name instead of writing code to find it.\n-static char *s_interface = NULL;\n+static char *\n+    s_interface = NULL;\n \n void\n zsys_set_interface (const char *interface_name)\n@@ -135,20 +136,21 @@ zsys_set_interface (const char *interface_name)\n \n //  Return network interface name to use for broadcasts.\n //  Returns "" if no interface was set.\n-//  This is experimental; may be merged into zbeacon class.\n \n char *\n zsys_interface (void)\n {\n-    if (s_interface) return s_interface;\n-\n-    // if the environment variable ZSYS_INTERFACE is set, use that as the\n-    // default interface name. This lets the environment variable be configured\n-    // for test environments where required. For example, on Mac OS X, zbeacon\n-    // cannot bind to 255.255.255.255 which is the default when there is no\n-    // specified interface.\n-    char* env = getenv ("ZSYS_INTERFACE");\n-    if (env) return env;\n+    if (s_interface)\n+        return s_interface;\n+\n+    //  If the environment variable ZSYS_INTERFACE is set, use that as\n+    //  the default interface name. This lets the environment variable\n+    //  be configured for test environments where required. For example,\n+    //  on Mac OS X, zbeacon cannot bind to 255.255.255.255 which is the\n+    //  default when there is no specified interface.\n+    char *env = getenv ("ZSYS_INTERFACE");\n+    if (env)\n+        return env;\n     return "";\n }\n \n', 'addition_count': 13, 'deletion_count': 11, 'src_file': 'a/src/zsys.c', 'tgt_file': 'b/src/zsys.c', 'file_extension': '.c', 'before_file': '/*  =========================================================================\n    zsys - system-level methods\n\n    Copyright (c) the Contributors as noted in the AUTHORS file.\n    This file is part of CZMQ, the high-level C binding for 0MQ:\n    http://czmq.zeromq.org.\n\n    This Source Code Form is subject to the terms of the Mozilla Public\n    License, v. 2.0. If a copy of the MPL was not distributed with this\n    file, You can obtain one at http://mozilla.org/MPL/2.0/.\n    =========================================================================\n*/\n\n/*\n@header\n    The zsys class provides a portable wrapper for system calls. We collect\n    them here to reduce the number of weird #ifdefs in other classes. As far\n    as possible, the bulk of CZMQ classes are fully portable.\n@discuss\n@end\n*/\n\n#include "czmq_classes.h"\n\n// For getcwd() variants\n#if (defined (WIN32))\n# include <direct.h>\n#else\n# include <unistd.h>\n#endif\n\n//  --------------------------------------------------------------------------\n//  Signal handling\n\n//  These are global variables accessible to CZMQ application code\nvolatile int zsys_interrupted = 0;  //  Current name\nvolatile int zctx_interrupted = 0;  //  Deprecated name\n\nstatic void s_signal_handler (int signal_value);\n\n//  We use these variables for signal handling\nstatic bool s_first_time = true;\nstatic bool handle_signals = true;\n#if defined (__UNIX__)\nstatic struct sigaction sigint_default;\nstatic struct sigaction sigterm_default;\n\n#elif defined (__WINDOWS__)\nstatic zsys_handler_fn *installed_handler_fn;\nstatic BOOL WINAPI\ns_handler_fn_shim (DWORD ctrltype)\n{\n    //  Return TRUE for events that we handle\n    if (ctrltype == CTRL_C_EVENT && installed_handler_fn != NULL) {\n        installed_handler_fn (ctrltype);\n        return TRUE;\n    }\n    if (ctrltype == CTRL_CLOSE_EVENT && installed_handler_fn != NULL) {\n        installed_handler_fn (ctrltype);\n        return TRUE;\n    }\n    return FALSE;\n}\n#endif\n\n//  --------------------------------------------------------------------------\n//  Global context handling\n\n//  ZeroMQ context for this process\nstatic void *s_process_ctx = NULL;\nstatic bool s_initialized = false;\nstatic bool s_shutting_down = false;\n#if defined (__UNIX__)\nstatic void zsys_cleanup (void);\n#endif\n\n#ifndef S_DEFAULT_ZSYS_FILE_STABLE_AGE_MSEC\n// This is a private tunable that is likely to be replaced or tweaked later\n// per comment block at s_zsys_file_stable() implementation, to reflect\n// the best stat data granularity available on host OS *and* used by czmq.\n#define S_DEFAULT_ZSYS_FILE_STABLE_AGE_MSEC 5000\n#endif\n\n//  Default globals for new sockets and other joys; these can all be set\n//  from the environment, or via the zsys_set_xxx API.\nstatic size_t s_io_threads = 1;     //  ZSYS_IO_THREADS=1\nstatic int s_thread_sched_policy = -1; //  ZSYS_THREAD_SCHED_POLICY=-1\nstatic int s_thread_priority = -1;  //  ZSYS_THREAD_PRIORITY=-1\nstatic int s_thread_name_prefix = -1;  //  ZSYS_THREAD_NAME_PREFIX=-1\nstatic char s_thread_name_prefix_str[16] = "0";  //  ZSYS_THREAD_NAME_PREFIX_STR="0"\nstatic size_t s_max_sockets = 1024; //  ZSYS_MAX_SOCKETS=1024\nstatic int s_max_msgsz = INT_MAX;   //  ZSYS_MAX_MSGSZ=INT_MAX\nstatic int64_t s_file_stable_age_msec = S_DEFAULT_ZSYS_FILE_STABLE_AGE_MSEC;\n                                    //  ZSYS_FILE_STABLE_AGE_MSEC=5000\nstatic size_t s_linger = 0;         //  ZSYS_LINGER=0\nstatic size_t s_sndhwm = 1000;      //  ZSYS_SNDHWM=1000\nstatic size_t s_rcvhwm = 1000;      //  ZSYS_RCVHWM=1000\nstatic size_t s_pipehwm = 1000;     //  ZSYS_PIPEHWM=1000\nstatic int s_ipv6 = 0;              //  ZSYS_IPV6=0\nstatic char *s_interface = NULL;    //  ZSYS_INTERFACE=\nstatic char *s_ipv6_address = NULL; //  ZSYS_IPV6_ADDRESS=\nstatic char *s_ipv6_mcast_address = NULL; //  ZSYS_IPV6_MCAST_ADDRESS=\nstatic int s_auto_use_fd = 0;       //  ZSYS_AUTO_USE_FD=0\nstatic char *s_logident = NULL;     //  ZSYS_LOGIDENT=\nstatic FILE *s_logstream = NULL;    //  ZSYS_LOGSTREAM=stdout/stderr\nstatic bool s_logsystem = false;    //  ZSYS_LOGSYSTEM=true/false\nstatic zsock_t *s_logsender = NULL;    //  ZSYS_LOGSENDER=\nstatic int s_zero_copy_recv = 1;    // ZSYS_ZERO_COPY_RECV=1\nstatic char *s_ipv4_mcast_address = NULL; //  ZSYS_IPV4_MCAST_ADDRESS=\nstatic unsigned char s_mcast_ttl = 1;     //  ZSYS_MCAST_TTL=1\n\n//  Track number of open sockets so we can zmq_term() safely\nstatic size_t s_open_sockets = 0;\n\n//  We keep a list of open sockets to report leaks to developers\nstatic zlist_t *s_sockref_list = NULL;\n\n//  This defines a single zsock_new() caller instance\ntypedef struct {\n    void *handle;\n    int type;\n    const char *filename;\n\n//  NOT thread safe, not a good design; this is to test the feasibility\n//  of forcing a network interface name instead of writing code to find it.\nstatic char *s_interface = NULL;\n\nvoid\nzsys_set_interface (const char *interface_name)\n#   define ZMUTEX_UNLOCK(m)  pthread_mutex_unlock (&m);\n#   define ZMUTEX_DESTROY(m) pthread_mutex_destroy (&m);\n#elif defined (__WINDOWS__)\ntypedef CRITICAL_SECTION zsys_mutex_t;\n#   define ZMUTEX_INIT(m)    InitializeCriticalSection (&m);\n\n//  Return network interface name to use for broadcasts.\n//  Returns "" if no interface was set.\n//  This is experimental; may be merged into zbeacon class.\n\nchar *\nzsys_interface (void)\n{\n    if (s_interface) return s_interface;\n\n    // if the environment variable ZSYS_INTERFACE is set, use that as the\n    // default interface name. This lets the environment variable be configured\n    // for test environments where required. For example, on Mac OS X, zbeacon\n    // cannot bind to 255.255.255.255 which is the default when there is no\n    // specified interface.\n    char* env = getenv ("ZSYS_INTERFACE");\n    if (env) return env;\n    return "";\n}\n\n//  Not threadsafe, so call only from main thread. Safe to call multiple\n//  times. Returns global CZMQ context.\n\n#if defined (__UNIX__)\n// mutex for pthread_once to run the init function only once in a process\nstatic pthread_once_t init_all_mutex_var = PTHREAD_ONCE_INIT;\n\n// handler to initialize mutexes one time in multi threaded env\nstatic void zsys_initialize_mutex() {\n    ZMUTEX_INIT (s_mutex);\n    ZMUTEX_INIT (s_init_mutex);\n}\n\n// handler to detect fork condition and cleanup the stale context inherited from parent process\nstatic void zsys_pthread_at_fork_handler(void) {\n    // re-initialize mutexes\n    ZMUTEX_INIT (s_init_mutex);\n    ZMUTEX_INIT (s_mutex);\n    // call cleanup\n    zsys_cleanup();\n}\n#endif\n\nvoid *\nzsys_init (void)\n{\n#if defined (__UNIX__)\n    //To avoid two inits at same time\n    pthread_once(&init_all_mutex_var, zsys_initialize_mutex);\n\n    if (s_initialized) {\n        assert (s_process_ctx);\n        return s_process_ctx;\n    }\n\n    ZMUTEX_LOCK (s_init_mutex);\n#endif\n\n    // Doing this again here... to ensure that after mutex wait if the thread 2 gets execution, it will\n    // will get the context right away\n    if (s_initialized) {\n        assert (s_process_ctx);\n#if defined (__UNIX__)\n        // unlock the mutex before returning the context\n        ZMUTEX_UNLOCK(s_init_mutex);\n#endif\n        return s_process_ctx;\n    }\n\n    //  Pull process defaults from environment\n    if (getenv ("ZSYS_IO_THREADS"))\n        s_io_threads = atoi (getenv ("ZSYS_IO_THREADS"));\n\n    if (getenv ("ZSYS_MAX_SOCKETS"))\n        s_max_sockets = atoi (getenv ("ZSYS_MAX_SOCKETS"));\n\n    if (getenv ("ZSYS_MAX_MSGSZ"))\n        s_max_msgsz = atoi (getenv ("ZSYS_MAX_MSGSZ"));\n\n    if (getenv ("ZSYS_ZERO_COPY_RECV"))\n        s_zero_copy_recv = atoi (getenv ("ZSYS_ZERO_COPY_RECV"));\n\n    if (getenv ("ZSYS_FILE_STABLE_AGE_MSEC"))\n        s_file_stable_age_msec = atoi (getenv ("ZSYS_FILE_STABLE_AGE_MSEC"));\n\n    if (getenv ("ZSYS_LINGER"))\n        s_linger = atoi (getenv ("ZSYS_LINGER"));\n\n    if (getenv ("ZSYS_SNDHWM"))\n        s_sndhwm = atoi (getenv ("ZSYS_SNDHWM"));\n\n    if (getenv ("ZSYS_RCVHWM"))\n        s_rcvhwm = atoi (getenv ("ZSYS_RCVHWM"));\n\n    if (getenv ("ZSYS_PIPEHWM"))\n        s_pipehwm = atoi (getenv ("ZSYS_PIPEHWM"));\n\n    if (getenv ("ZSYS_IPV6"))\n        s_ipv6 = atoi (getenv ("ZSYS_IPV6"));\n\n    if (getenv ("ZSYS_LOGSTREAM")) {\n        if (streq (getenv ("ZSYS_LOGSTREAM"), "stdout"))\n            s_logstream = stdout;\n        else\n        if (streq (getenv ("ZSYS_LOGSTREAM"), "stderr"))\n            s_logstream = stderr;\n    }\n    else\n        s_logstream = stdout;\n\n    if (getenv ("ZSYS_LOGSYSTEM")) {\n        if (streq (getenv ("ZSYS_LOGSYSTEM"), "true"))\n            s_logsystem = true;\n        else\n        if (streq (getenv ("ZSYS_LOGSYSTEM"), "false"))\n            s_logsystem = false;\n    }\n\n    if (getenv ("ZSYS_AUTO_USE_FD"))\n        s_auto_use_fd = atoi (getenv ("ZSYS_AUTO_USE_FD"));\n\n    zsys_catch_interrupts ();\n\n#if defined (__WINDOWS__)\n    ZMUTEX_INIT (s_mutex);\n#endif\n    s_sockref_list = zlist_new ();\n    if (!s_sockref_list) {\n        zsys_shutdown ();\n#if defined (__UNIX__)\n        ZMUTEX_UNLOCK(s_init_mutex);\n#endif\n        return NULL;\n    }\n    srandom ((unsigned) time (NULL));\n\n    assert (!s_process_ctx);\n    //  We use zmq_init/zmq_term to keep compatibility back to ZMQ v2\n    s_process_ctx = zmq_init ((int) s_io_threads);\n#if defined (ZMQ_MAX_SOCKETS)\n    zmq_ctx_set (s_process_ctx, ZMQ_MAX_SOCKETS, (int) s_max_sockets);\n#endif\n    s_initialized = true;\n\n#if defined (__UNIX__)\n    atexit (zsys_shutdown);\n    pthread_atfork(NULL, NULL, &zsys_pthread_at_fork_handler);\n    //don\'t hold the lock because some of the function will call zsys_init again\n    ZMUTEX_UNLOCK(s_init_mutex);\n#endif\n\n    //  The following functions call zsys_init(), so they MUST be called after\n    //  s_initialized is set in order to avoid an infinite recursion\n    if (getenv ("ZSYS_INTERFACE"))\n        zsys_set_interface (getenv ("ZSYS_INTERFACE"));\n\n    if (getenv ("ZSYS_IPV6_ADDRESS"))\n        zsys_set_ipv6_address (getenv ("ZSYS_IPV6_ADDRESS"));\n\n    if (getenv ("ZSYS_IPV6_MCAST_ADDRESS"))\n        zsys_set_ipv6_mcast_address (getenv ("ZSYS_IPV6_MCAST_ADDRESS"));\n    else\n        zsys_set_ipv6_mcast_address ("ff02:0:0:0:0:0:0:1");\n\n    if (getenv ("ZSYS_IPV4_MCAST_ADDRESS"))\n        zsys_set_ipv4_mcast_address (getenv ("ZSYS_IPV4_MCAST_ADDRESS"));\n    else\n        zsys_set_ipv4_mcast_address (NULL);\n\n\n    if (getenv ("ZSYS_LOGIDENT"))\n        zsys_set_logident (getenv ("ZSYS_LOGIDENT"));\n\n    if (getenv ("ZSYS_LOGSENDER"))\n        zsys_set_logsender (getenv ("ZSYS_LOGSENDER"));\n\n    zsys_set_max_msgsz (s_max_msgsz);\n\n#if defined ZMQ_ZERO_COPY_RECV\n    zmq_ctx_set (s_process_ctx, ZMQ_ZERO_COPY_RECV, s_zero_copy_recv);\n#endif\n\n    zsys_set_file_stable_age_msec (s_file_stable_age_msec);\n\n    if (getenv ("ZSYS_THREAD_PRIORITY"))\n        zsys_set_thread_priority (atoi (getenv ("ZSYS_THREAD_PRIORITY")));\n    else\n        zsys_set_thread_priority (s_thread_priority);\n\n    if (getenv ("ZSYS_THREAD_SCHED_POLICY"))\n        zsys_set_thread_sched_policy (atoi (getenv ("ZSYS_THREAD_SCHED_POLICY")));\n    else\n        zsys_set_thread_sched_policy (s_thread_sched_policy);\n\n    if (getenv ("ZSYS_THREAD_NAME_PREFIX"))\n        zsys_set_thread_name_prefix (atoi (getenv ("ZSYS_THREAD_NAME_PREFIX")));\n    else\n        zsys_set_thread_name_prefix (s_thread_name_prefix);\n\n    if (getenv ("ZSYS_THREAD_NAME_PREFIX_STR"))\n        zsys_set_thread_name_prefix_str (getenv ("ZSYS_THREAD_NAME_PREFIX_STR"));\n\n    return s_process_ctx;\n}\n\n//  atexit or manual termination for the process\nvoid\nzsys_shutdown (void)\n{\n    if (!s_initialized || s_shutting_down)\n        return;\n\n    s_shutting_down = true;\n\n    //  The atexit handler is called when the main function exits;\n    //  however we may have zactor threads shutting down and still\n    //  trying to close their sockets. So if we suspect there are\n    //  actors busy (s_open_sockets > 0), then we sleep for a few\n    //  hundred milliseconds to allow the actors, if any, to get in\n    //  and close their sockets.\n    ZMUTEX_LOCK (s_mutex);\n    size_t busy = s_open_sockets;\n    ZMUTEX_UNLOCK (s_mutex);\n    if (busy)\n        zclock_sleep (200);\n\n    //  No matter, we are now going to shut down\n    //  Print the source reference for any sockets the app did not\n    //  destroy properly.\n    ZMUTEX_LOCK (s_mutex);\n    s_sockref_t *sockref = (s_sockref_t *) zlist_pop (s_sockref_list);\n    while (sockref) {\n        assert (sockref->filename);\n        zsys_error ("[%d]dangling \'%s\' socket created at %s:%d",\n                    getpid (),\n                    zsys_sockname (sockref->type),\n                    sockref->filename, (int) sockref->line_nbr);\n        zmq_close (sockref->handle);\n        freen (sockref);\n        sockref = (s_sockref_t *) zlist_pop (s_sockref_list);\n        --s_open_sockets;\n    }\n    zlist_destroy (&s_sockref_list);\n    ZMUTEX_UNLOCK (s_mutex);\n\n    //  Close logsender socket if opened (don\'t do this in critical section)\n    if (s_logsender)\n        zsock_destroy (&s_logsender);\n\n    if (s_open_sockets == 0)\n    {\n      zmq_term(s_process_ctx);\n      s_process_ctx = NULL;\n      s_io_threads = 1;\n      s_thread_sched_policy = -1;\n      s_thread_priority = -1;\n      s_thread_name_prefix = -1;\n      strcpy (s_thread_name_prefix_str, "0");\n      s_max_sockets = 1024;\n      s_max_msgsz = INT_MAX;\n      s_file_stable_age_msec = S_DEFAULT_ZSYS_FILE_STABLE_AGE_MSEC;\n      s_linger = 0;\n      s_sndhwm = 1000;\n      s_rcvhwm = 1000;\n      s_pipehwm = 1000;\n      s_ipv6 = 0;\n      s_auto_use_fd = 0;\n      s_logstream = NULL;\n      s_logsystem = false;\n    }\n    else\n        zsys_error ("dangling sockets: cannot terminate ZMQ safely");\n\n    ZMUTEX_DESTROY (s_mutex);\n\n    //  Free dynamically allocated properties\n    freen (s_interface);\n    freen (s_ipv6_address);\n    freen (s_ipv6_mcast_address);\n    freen (s_logident);\n\n    zsys_interrupted = 0;\n    zctx_interrupted = 0;\n\n    zsys_handler_reset ();\n\n#if defined (__UNIX__)\n    closelog ();                //  Just to be pedantic\n#endif\n\n    s_initialized = false;\n    s_shutting_down = false;\n}\n\n#if defined (__UNIX__)\n//  Restores all CZMQ global state to initial values\nstatic void\nzsys_cleanup (void)\n{\n    ZMUTEX_LOCK (s_init_mutex);\n    s_process_ctx = NULL;\n    zsys_interrupted = 0;\n    zctx_interrupted = 0;\n\n    s_first_time = true;\n    handle_signals = true;\n\n    s_initialized = false;\n\n    s_io_threads = 1;\n    s_max_sockets = 1024;\n    s_max_msgsz = INT_MAX;\n    s_linger = 0;\n    s_sndhwm = 1000;\n    s_rcvhwm = 1000;\n    s_pipehwm = 1000;\n    s_ipv6 = 0;\n    s_interface = NULL;\n    s_ipv6_address = NULL;\n    s_ipv6_mcast_address = NULL;\n    s_auto_use_fd = 0;\n    s_logident = NULL;\n    s_logstream = NULL;\n    s_logsystem = false;\n    s_logsender = NULL;\n\n    s_open_sockets = 0;\n    ZMUTEX_UNLOCK (s_init_mutex);\n}\n#endif\n\n//  --------------------------------------------------------------------------\n//  Get a new ZMQ socket, automagically creating a ZMQ context if this is\n//  the first time. Caller is responsible for destroying the ZMQ socket\n//  before process exits, to avoid a ZMQ deadlock. Note: you should not use\n//  this method in CZMQ apps, use zsock_new() instead. This is for system\n//  use only, really.\n\nvoid *\nzsys_socket (int type, const char *filename, size_t line_nbr)\n{\n    //  First time initialization; if the application is mixing\n    //  its own threading calls with zsock, this may fail if two\n    //  threads try to create sockets at the same time. In such\n    //  apps, they MUST create a socket in the main program before\n    //  starting any threads. If the app uses zactor for its threads\n    //  then we can guarantee this to always be safe.\n    zsys_init ();\n    ZMUTEX_LOCK (s_mutex);\n    void *handle = zmq_socket (s_process_ctx, type);\n    if (handle) {\n        //  Configure socket with process defaults\n        zsock_set_linger (handle, (int) s_linger);\n#if (ZMQ_VERSION_MAJOR == 2)\n        // TODO: v2/v3 socket api in zsock_option.inc are not public (not\n        // added to include/zsock.h) so we have to use zmq_setsockopt directly\n        // This should be fixed and zsock_set_hwm should be used instead\n#       if defined (ZMQ_HWM)\n        uint64_t value = s_sndhwm;\n        int rc = zmq_setsockopt (handle, ZMQ_HWM, &value, sizeof (uint64_t));\n        assert (rc == 0 || zmq_errno () == ETERM);\n#       endif\n#else\n        //  For later versions we use separate SNDHWM and RCVHWM\n        zsock_set_sndhwm (handle, (int) s_sndhwm);\n        zsock_set_rcvhwm (handle, (int) s_rcvhwm);\n#   if defined (ZMQ_IPV6)\n        zsock_set_ipv6 (handle, s_ipv6);\n#   else\n        zsock_set_ipv4only (handle, s_ipv6? 0: 1);\n#   endif\n#endif\n        //  Add socket to reference tracker so we can report leaks; this is\n        //  done only when the caller passes a filename/line_nbr\n        if (filename) {\n            s_sockref_t *sockref = (s_sockref_t *) zmalloc (sizeof (s_sockref_t));\n            if (sockref) {\n                sockref->handle = handle;\n                sockref->type = type;\n                sockref->filename = filename;\n                sockref->line_nbr = line_nbr;\n                zlist_append (s_sockref_list, sockref);\n            }\n            else {\n                zmq_close (handle);\n                ZMUTEX_UNLOCK (s_mutex);\n                return NULL;\n            }\n        }\n        s_open_sockets++;\n    }\n    ZMUTEX_UNLOCK (s_mutex);\n    return handle;\n}\n\n//  --------------------------------------------------------------------------\n//  Destroy/close a ZMQ socket. You should call this for every socket you\n//  create using zsys_socket().\n\nint\nzsys_close (void *handle, const char *filename, size_t line_nbr)\n{\n    ZMUTEX_LOCK (s_mutex);\n    //  It\'s possible atexit() has already happened if we\'re running under\n    //  a debugger that redirects the main thread exit.\n    if (s_sockref_list) {\n        s_sockref_t *sockref = (s_sockref_t *) zlist_first (s_sockref_list);\n        while (sockref) {\n            if (sockref->handle == handle) {\n                zlist_remove (s_sockref_list, sockref);\n                freen (sockref);\n                break;\n            }\n            sockref = (s_sockref_t *) zlist_next (s_sockref_list);\n        }\n    }\n    s_open_sockets--;\n    zmq_close (handle);\n    ZMUTEX_UNLOCK (s_mutex);\n    return 0;\n}\n\n\n//  --------------------------------------------------------------------------\n//  Return ZMQ socket name for socket type\n\nchar *\nzsys_sockname (int socktype)\n{\n    char *type_names [] = {\n        "PAIR", "PUB", "SUB", "REQ", "REP",\n        "DEALER", "ROUTER", "PULL", "PUSH",\n        "XPUB", "XSUB", "STREAM",\n        "SERVER", "CLIENT",\n        "RADIO", "DISH",\n        "SCATTER", "GATHER", "DGRAM"\n    };\n    //  This array matches ZMQ_XXX type definitions\n    assert (ZMQ_PAIR == 0);\n#if defined (ZMQ_DGRAM)\n    assert (socktype >= 0 && socktype <= ZMQ_DGRAM);\n#elif defined (ZMQ_SCATTER)\n    assert (socktype >= 0 && socktype <= ZMQ_SCATTER);\n#elif defined (ZMQ_DISH)\n    assert (socktype >= 0 && socktype <= ZMQ_DISH);\n#elif defined (ZMQ_CLIENT)\n    assert (socktype >= 0 && socktype <= ZMQ_CLIENT);\n#elif defined (ZMQ_STREAM)\n    assert (socktype >= 0 && socktype <= ZMQ_STREAM);\n#else\n    assert (socktype >= 0 && socktype <= ZMQ_XSUB);\n#endif\n    return type_names [socktype];\n}\n\n\n//  --------------------------------------------------------------------------\n//  Create a pipe, which consists of two PAIR sockets connected over inproc.\n//  The pipe is configured to use the zsys_pipehwm setting. Returns the\n//  frontend socket successful, NULL if failed.\n\nzsock_t *\nzsys_create_pipe (zsock_t **backend_p)\n{\n    zsock_t *frontend = zsock_new (ZMQ_PAIR);\n    zsock_t *backend = zsock_new (ZMQ_PAIR);\n    assert (frontend);\n    assert (backend);\n\n#if (ZMQ_VERSION_MAJOR == 2)\n    // TODO: v2/v3 socket api in zsock_option.inc are not public (not\n    // added to include/zsock.h) so we have to use zmq_setsockopt directly\n    // This should be fixed and zsock_set_hwm should be used instead\n#   if defined (ZMQ_HWM)\n    uint64_t value = zsys_pipehwm ();\n    int ret = zmq_setsockopt (zsock_resolve (frontend), ZMQ_HWM, &value,\n        sizeof (uint64_t));\n    assert (ret == 0 || zmq_errno () == ETERM);\n    value = zsys_pipehwm ();\n    ret = zmq_setsockopt (zsock_resolve (backend), ZMQ_HWM, &value,\n        sizeof (uint64_t));\n    assert (ret == 0 || zmq_errno () == ETERM);\n#   endif\n#else\n    zsock_set_sndhwm (frontend, (int) zsys_pipehwm ());\n    zsock_set_sndhwm (backend, (int) zsys_pipehwm ());\n#endif\n    //  Now bind and connect pipe ends\n    char endpoint [32];\n    while (true) {\n        sprintf (endpoint, "inproc://pipe-%04x-%04x",\n                 randof (0x10000), randof (0x10000));\n        if (zsock_bind (frontend, "%s", endpoint) == 0)\n            break;\n    }\n    int rc = zsock_connect (backend, "%s", endpoint);\n    assert (rc != -1);          //  Connect cannot fail\n\n    //  Return frontend and backend sockets\n    *backend_p = backend;\n    return frontend;\n}\n\n\n//  --------------------------------------------------------------------------\n//  Set interrupt handler; this saves the default handlers so that a\n//  zsys_handler_reset () can restore them. If you call this multiple times\n//  then the last handler will take affect. If handler_fn is NULL, disables\n//  default SIGINT/SIGTERM handling in CZMQ.\n\nvoid\nzsys_handler_set (zsys_handler_fn *handler_fn)\n{\n    if (!handler_fn) {\n        //  Disable existing or future signal handling\n        zsys_handler_reset ();\n        handle_signals = false;\n    }\n    else {\n        handle_signals = true;\n#if defined (__UNIX__)\n        if (s_first_time) {\n            //  If first time, save default handlers\n            sigaction (SIGINT, NULL, &sigint_default);\n            sigaction (SIGTERM, NULL, &sigterm_default);\n            s_first_time = false;\n        }\n        //  Install signal handler for SIGINT and SIGTERM\n        struct sigaction action;\n        action.sa_handler = handler_fn;\n        action.sa_flags = 0;\n        sigemptyset (&action.sa_mask);\n        sigaction (SIGINT, &action, NULL);\n        sigaction (SIGTERM, &action, NULL);\n#elif defined (__WINDOWS__)\n        installed_handler_fn = handler_fn;\n        if (s_first_time) {\n            SetConsoleCtrlHandler (s_handler_fn_shim, TRUE);\n            s_first_time = false;\n        }\n#else\n#   error "No signal handling defined for this platform"\n#endif\n    }\n}\n\n\n//  --------------------------------------------------------------------------\n//  Reset interrupt handler, call this at exit if needed\n//  Idempotent; safe to call multiple times\n\nvoid\nzsys_handler_reset (void)\n{\n#if defined (__UNIX__)\n    //  Restore default handlers if not already done\n    if (handle_signals && !s_first_time) {\n        sigaction (SIGINT, &sigint_default, NULL);\n        sigaction (SIGTERM, &sigterm_default, NULL);\n        sigint_default.sa_handler = NULL;\n        sigterm_default.sa_handler = NULL;\n        s_first_time = true;\n    }\n#elif defined (__WINDOWS__)\n    if (handle_signals && !s_first_time) {\n        SetConsoleCtrlHandler (s_handler_fn_shim, FALSE);\n        installed_handler_fn = NULL;\n        s_first_time = true;\n    }\n#endif\n}\n\n\n//  --------------------------------------------------------------------------\n//  Set default interrupt handler, so Ctrl-C or SIGTERM will set\n//  zsys_interrupted. Idempotent; safe to call multiple times.\n\nvoid\nzsys_catch_interrupts (void)\n{\n    //  Catch SIGINT and SIGTERM unless ZSYS_SIGHANDLER=false\n    if ((getenv ("ZSYS_SIGHANDLER") == NULL\n    ||   strneq (getenv ("ZSYS_SIGHANDLER"), "false"))\n    &&   handle_signals)\n        zsys_handler_set (s_signal_handler);\n}\n\n//  Default internal signal handler\nstatic void\ns_signal_handler (int signal_value)\n{\n    zctx_interrupted = 1;\n    zsys_interrupted = 1;\n}\n\n\n//  --------------------------------------------------------------------------\n//  Return true if file exists, else zero\n\nbool\nzsys_file_exists (const char *filename)\n{\n    assert (filename);\n    return zsys_file_mode (filename) != -1;\n}\n\n\n//  --------------------------------------------------------------------------\n//  Return size of file, or -1 if not found\n\nssize_t\nzsys_file_size (const char *filename)\n{\n    struct stat\n        stat_buf;\n\n    assert (filename);\n    if (stat ((char *) filename, &stat_buf) == 0)\n        return stat_buf.st_size;\n    else\n        return -1;\n}\n\n\n//  --------------------------------------------------------------------------\n//  Return file modification time (accounted in seconds usually since\n//  UNIX Epoch, with granularity dependent on underlying filesystem,\n//  and starting point dependent on host OS and maybe its bitness).\n//  Per https://msdn.microsoft.com/en-us/library/w4ddyt9h(vs.71).aspx :\n//      Note   In all versions of Microsoft C/C++ except Microsoft C/C++\n//      version 7.0, and in all versions of Microsoft Visual C++, the time\n//      function returns the current time as the number of seconds elapsed\n//      since midnight on January 1, 1970. In Microsoft C/C++ version 7.0,\n//      time() returned the current time as the number of seconds elapsed\n//      since midnight on December 31, 1899.\n//  This value is "arithmetic" with no big guarantees in the standards, and\n//  normally it should be manipulated with host\'s datetime suite of routines,\n//  including difftime(), or converted to "struct tm" for any predictable use.\n//  Returns 0 if the file does not exist.\n\ntime_t\nzsys_file_modified (const char *filename)\n{\n    struct stat stat_buf;\n    if (stat (filename, &stat_buf) == 0)\n        return stat_buf.st_mtime;\n    else\n        return 0;\n}\n\n\n//  --------------------------------------------------------------------------\n//  Return file mode; provides at least support for the POSIX S_ISREG(m)\n//  and S_ISDIR(m) macros and the S_IRUSR and S_IWUSR bits, on all boxes.\n//  Returns a mode_t cast to int, or -1 in case of error.\n\nint\nzsys_file_mode (const char *filename)\n{\n#if (defined (__WINDOWS__))\n    DWORD dwfa = GetFileAttributesA (filename);\n    if (dwfa == 0xffffffff)\n        return -1;\n\n    dbyte mode = 0;\n    if (dwfa & FILE_ATTRIBUTE_DIRECTORY)\n        mode |= S_IFDIR;\n    else\n        mode |= S_IFREG;\n\n    if (!(dwfa & FILE_ATTRIBUTE_HIDDEN))\n        mode |= S_IRUSR;\n    if (!(dwfa & FILE_ATTRIBUTE_READONLY))\n        mode |= S_IWUSR;\n\n    return mode;\n#else\n    struct stat stat_buf;\n    if (stat ((char *) filename, &stat_buf) == 0)\n        return stat_buf.st_mode;\n    else\n        return -1;\n#endif\n}\n\n\n//  --------------------------------------------------------------------------\n//  Delete file, return 0 if OK, -1 if not possible.\n\nint\nzsys_file_delete (const char *filename)\n{\n    assert (filename);\n#if (defined (__WINDOWS__))\n    return DeleteFileA (filename)? 0: -1;\n#else\n    return unlink (filename);\n#endif\n}\n\n\n//  --------------------------------------------------------------------------\n//  Check if file is \'stable\'\n\n// Internal implementation rigged with debugs and called from selftest\nstatic bool\ns_zsys_file_stable (const char *filename, bool verbose)\n{\n    struct stat stat_buf;\n    if (stat (filename, &stat_buf) == 0) {\n        //  File is \'stable\' if older (per filesystem stats) than a threshold.\n        //  This used to mean more than 1 second old, counted in microseconds\n        //  after inflating the st_mtime data - but this way of calculation\n        //  has a caveat: if we created the file at Nsec.999msec, or rather\n        //  the FS metadata was updated at that point, the st_mtime will be\n        //  (after inflation) N.000. So a few milliseconds later, at (N+1)sec,\n        //  we find the age difference seems over 1000 so the file is 1 sec\n        //  old - even though it has barely been created. Compounding the\n        //  issue, some filesystems have worse timestamp precision - e.g. the\n        //  FAT filesystem variants are widespread (per SD standards) on\n        //  removable media, and only account even seconds in stat data.\n        //  Solutions are two-fold: when using stat fields that are precise\n        //  to a second (or inpredictably two), we should actually check for\n        //  (age > 3000+) in rounded-microsecond accounting. Also, for some\n        //  systems we can have `configure`-time checks on presence of more\n        //  precise (and less standardized) stat timestamp fields, where we\n        //  can presumably avoid rounding to thousands and use (age > 2000).\n        //  It might also help to define a zsys_file_modified_msec() whose\n        //  actual granularity will be OS-dependent (rounded to 1000 or not).\n        //  These are TODO ideas for subsequent work.\n\n#if (defined (WIN32))\n#   define EPOCH_DIFFERENCE 11644473600LL\n        long age = (long) (zclock_time () - EPOCH_DIFFERENCE * 1000 - (stat_buf.st_mtime * 1000));\n        if (verbose)\n            zsys_debug ("zsys_file_stable@WIN32: file \'%s\' age is %ld msec "\n                "at timestamp %" PRIi64 " where st_mtime was %jd adjusted by %jd",\n                filename, age, zclock_time (), (intmax_t)(stat_buf.st_mtime * 1000),\n                (intmax_t)(EPOCH_DIFFERENCE * 1000) );\n#else\n        long age = (long) (zclock_time () - (stat_buf.st_mtime * 1000));\n        if (verbose)\n            zsys_debug ("zsys_file_stable@non-WIN32: file \'%s\' age is %ld msec "\n                "at timestamp %" PRIi64 " where st_mtime was %jd",\n                filename, age, zclock_time (), (intmax_t)(stat_buf.st_mtime * 1000) );\n#endif\n        return (age > s_file_stable_age_msec);\n    }\n    else {\n        if (verbose)\n            zsys_debug ("zsys_file_stable: could not stat file \'%s\'", filename);\n        return false;           //  File doesn\'t exist, so not stable\n    }\n}\n\n// Public implementation does not do debugs\nbool\nzsys_file_stable (const char *filename)\n{\n    return s_zsys_file_stable(filename, false);\n}\n\n//  --------------------------------------------------------------------------\n//  Create a file path if it doesn\'t exist. The file path is treated as a\n//  printf format.\n\nint\nzsys_dir_create (const char *pathname, ...)\n{\n    va_list argptr;\n    va_start (argptr, pathname);\n    char *formatted = zsys_vprintf (pathname, argptr);\n    va_end (argptr);\n    if (!formatted)\n        return -1;\n\n    //  Create parent directory levels if needed\n    char *slash = strchr (formatted + 1, \'/\');\n    while (true) {\n        if (slash)\n            *slash = 0;         //  Cut at slash\n        int mode = zsys_file_mode (formatted);\n        if (mode == -1) {\n            //  Does not exist, try to create it\n#if (defined (__WINDOWS__))\n            if (!CreateDirectoryA (formatted, NULL)) {\n#else\n            if (mkdir (formatted, 0775)) {\n#endif\n                freen (formatted);\n                return -1;      //  Failed\n            }\n        }\n        else\n        if ((mode & S_IFDIR) == 0) {\n            //  Not a directory, abort\n        }\n        if (!slash)             //  End if last segment\n            break;\n        *slash = \'/\';\n        slash = strchr (slash + 1, \'/\');\n    }\n    zstr_free (&formatted);\n    return 0;\n}\n\n\n//  --------------------------------------------------------------------------\n//  Remove a file path if empty; the pathname is treated as printf format.\n\nint\nzsys_dir_delete (const char *pathname, ...)\n{\n    va_list argptr;\n    va_start (argptr, pathname);\n    char *formatted = zsys_vprintf (pathname, argptr);\n    va_end (argptr);\n    if (!formatted)\n        return -1;\n\n#if (defined (__WINDOWS__))\n    int rc = RemoveDirectoryA (formatted)? 0: -1;\n#else\n    int rc = rmdir (formatted);\n#endif\n    zstr_free (&formatted);\n    return rc;\n}\n\n\n//  --------------------------------------------------------------------------\n//  Move to a specified working directory. Returns 0 if OK, -1 if this failed.\n\nint\nzsys_dir_change (const char *pathname)\n{\n    assert (pathname);\n#if (defined (__UNIX__))\n    return chdir (pathname);\n#elif (defined (__WINDOWS__))\n    return !SetCurrentDirectoryA (pathname);\n#endif\n    return -1;              //  Not implemented\n}\n\n\n//  --------------------------------------------------------------------------\n//  Set private file creation mode; all files created from here will be\n//  readable/writable by the owner only.\n\n#if !defined (__WINDOWS__)\nstatic mode_t s_old_mask = 0;\n#endif\n\nvoid\nzsys_file_mode_private (void)\n{\n#if !defined (__WINDOWS__)\n    s_old_mask = umask (S_IWGRP | S_IWOTH | S_IRGRP | S_IROTH);\n#endif\n}\n\n\n//  --------------------------------------------------------------------------\n//  Reset default file creation mode; all files created from here will use\n//  process file mode defaults.\n\nvoid\nzsys_file_mode_default (void)\n{\n    //  Reset process file create mask\n#if !defined (__WINDOWS__)\n    if (s_old_mask)\n        umask (s_old_mask);\n#endif\n}\n\n\n//  --------------------------------------------------------------------------\n//  Return the CZMQ version for run-time API detection; returns version\n//  number into provided fields, providing reference isn\'t null in each case.\n\nvoid\nzsys_version (int *major, int *minor, int *patch)\n{\n    if (major)\n        *major = CZMQ_VERSION_MAJOR;\n    if (minor)\n        *minor = CZMQ_VERSION_MINOR;\n    if (patch)\n        *patch = CZMQ_VERSION_PATCH;\n}\n\n\n//  --------------------------------------------------------------------------\n//  Format a string using printf formatting, returning a freshly allocated\n//  buffer. If there was insufficient memory, returns NULL. Free the returned\n//  string using zstr_free(). The hinted version allows to optimize by using\n//  a larger starting buffer size (known to/assumed by the developer) and so\n//  avoid reallocations.\n\nchar *\nzsys_sprintf_hint (int hint, const char *format, ...)\n{\n    va_list argptr;\n    va_start (argptr, format);\n    char *string = s_zsys_vprintf_hint (hint, format, argptr);\n    va_end (argptr);\n    return (string);\n}\n\n//  --------------------------------------------------------------------------\n//  Format a string using printf formatting, returning a freshly allocated\n//  buffer. If there was insufficient memory, returns NULL. Free the returned\n//  string using zstr_free().\n\nchar *\nzsys_sprintf (const char *format, ...)\n{\n    // Effectively this is a copy of the small zsys_sprintf_hint with\n    // hardcoded hint value; this is is cheaper than parsing va_list\n    // several times to just call the other implementation cleanly.\n    va_list argptr;\n    va_start (argptr, format);\n    char *string = s_zsys_vprintf_hint (256, format, argptr);\n    va_end (argptr);\n    return (string);\n}\n\n//  --------------------------------------------------------------------------\n//  Format a string with variable arguments, returning a freshly allocated\n//  buffer. If there was insufficient memory, returns NULL. Free the returned\n//  string using zstr_free().\n\nstatic inline\nchar *\ns_zsys_vprintf_hint (int hint, const char *format, va_list argptr)\n{\n    if (hint <= 0) {\n        //  The hint is not a hard requrement so no error here.\n        //  Just fall back to legacy default.\n        hint = 256;\n    }\n    // Must use int "size" to compare to "required" (from vsnprintf)\n    int size = hint;\n    char *string = (char *) malloc (size);\n    if (!string)\n        return NULL;\n\n    //  Using argptr is destructive, so we take a copy each time we need it\n    //  We define va_copy for Windows in czmq_prelude.h\n    va_list my_argptr;\n    va_copy (my_argptr, argptr);\n    int required = vsnprintf (string, size, format, my_argptr);\n    va_end (my_argptr);\n#ifdef __WINDOWS__\n    if (required < 0 || required >= size) {\n        va_copy (my_argptr, argptr);\n#ifdef _MSC_VER\n        required = _vscprintf (format, argptr);\n#else\n        required = vsnprintf (NULL, 0, format, argptr);\n#endif\n        va_end (my_argptr);\n    }\n#endif\n    if (required < 0) {\n        // vsnprintf failed at unknown point; at least prohibit\n        // accesses by string consumers into random memory\n        string [hint - 1] = \'\\0\';\n        return string;\n    }\n    //  If formatted string cannot fit into small string, reallocate a\n    //  larger buffer for it. If it did fit, we only called vsnprintf()\n    //  once and already have the good result, so pre-counting with a\n    //  NULL string would not be beneficial for shorter texts (always\n    //  calling vsnprintf() twice then).\n    if (required >= hint) {\n        size = required + 1;\n        freen (string);\n        string = (char *) malloc (size);\n        if (string) {\n            va_copy (my_argptr, argptr);\n            vsnprintf (string, size, format, my_argptr);\n            va_end (my_argptr);\n        }\n    }\n    return string;\n}\n\nchar *\nzsys_vprintf (const char *format, va_list argptr)\n{\n    return (s_zsys_vprintf_hint(256, format, argptr));\n}\n\n//  --------------------------------------------------------------------------\n//  Create a UDP beacon socket; if the routable option is true, uses\n//  multicast (not yet implemented), else uses broadcast. This method\n//  and related ones might _eventually_ be moved to a zudp class.\n\nSOCKET\nzsys_udp_new (bool routable)\n{\n    //IPV6 Multicast not implemented yet so only allow routable if IPv4\n    assert ((routable && !zsys_ipv6 ()) || !routable);\n    SOCKET udpsock;\n    int type = SOCK_DGRAM;\n#ifdef CZMQ_HAVE_SOCK_CLOEXEC\n    //  Ensure socket is closed by exec() functions.\n    type |= SOCK_CLOEXEC;\n#endif\n\n    if (zsys_ipv6 ())\n        udpsock = socket (AF_INET6, type, IPPROTO_UDP);\n    else\n        udpsock = socket (AF_INET, type, IPPROTO_UDP);\n    if (udpsock == INVALID_SOCKET) {\n        zsys_socket_error ("socket");\n        return INVALID_SOCKET;\n    }\n\n    //  On Windows, preventing sockets to be inherited by child processes.\n#if defined (__WINDOWS__) && defined (HANDLE_FLAG_INHERIT)\n    if (!SetHandleInformation ((HANDLE) udpsock, HANDLE_FLAG_INHERIT, 0))\n        zsys_socket_error ("SetHandleInformation (HANDLE_FLAG_INHERIT)");\n#endif\n\n    //  Ask operating system for broadcast permissions on socket\n    int on = 1;\n    if (setsockopt (udpsock, SOL_SOCKET, SO_BROADCAST,\n                    (char *) &on, sizeof (on)) == SOCKET_ERROR)\n        zsys_socket_error ("setsockopt (SO_BROADCAST)");\n\n    //  Allow multiple owners to bind to socket; incoming\n    //  messages will replicate to each owner\n    if (setsockopt (udpsock, SOL_SOCKET, SO_REUSEADDR,\n                    (char *) &on, sizeof (on)) == SOCKET_ERROR)\n        zsys_socket_error ("setsockopt (SO_REUSEADDR)");\n\n#if defined (SO_REUSEPORT)\n    //  On some platforms we have to ask to reuse the port\n    if (setsockopt (udpsock, SOL_SOCKET, SO_REUSEPORT,\n                    (char *) &on, sizeof (on)) == SOCKET_ERROR)\n        zsys_socket_error ("setsockopt (SO_REUSEPORT)");\n#endif\n\n    //  Only set TLL for IPv4\n    if (routable && zsys_mcast_ttl () > 1) {\n        int ttl = zsys_mcast_ttl ();\n        if (setsockopt (udpsock, IPPROTO_IP, IP_MULTICAST_TTL, (const char *) &ttl,\n                        sizeof (ttl))\n            == SOCKET_ERROR)\n            zsys_socket_error ("setsockopt (IP_MULTICAST_TTL)");\n    }\n\n    //  TODO\n    //  Set TLL for IPv6\n    /*if (routable && zsys_ipv6 ()) {\n       int ttl = zsys_mcast_ttl ();\n        if (setsockopt (udpsock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,\n                (char *) &ttl, sizeof (ttl))\n            == SOCKET_ERROR)\n            zsys_socket_error ("setsockopt (IP_MULTICAST_TTL)");\n    }*/\n    return udpsock;\n}\n\n\n//  --------------------------------------------------------------------------\n//  Close a UDP socket\n\nint\nzsys_udp_close (SOCKET handle)\n{\n#if (defined (__WINDOWS__))\n    return closesocket (handle);\n#else\n    return close (handle);\n#endif\n}\n\n\n//  --------------------------------------------------------------------------\n//  Send zframe to UDP socket, return -1 if sending failed due to\n//  interface having disappeared (happens easily with WiFi)\n\nint\nzsys_udp_send (SOCKET udpsock, zframe_t *frame, inaddr_t *address, int addrlen)\n{\n    assert (frame);\n    assert (address);\n\n    if (sendto (udpsock,\n        (char *) zframe_data (frame), (int) zframe_size (frame),\n        0, //  Flags\n        (struct sockaddr *) address, addrlen) == -1) {\n        zsys_debug ("zsys_udp_send: failed, reason=%s", strerror (errno));\n        return -1;              //  UDP broadcast not possible\n    }\n    else\n        return 0;\n}\n\n\n//  --------------------------------------------------------------------------\n//  Receive zframe from UDP socket, and set address of peer that sent it\n//  The peername must be a char [INET_ADDRSTRLEN] array if IPv6 is disabled or\n//  NI_MAXHOST if it\'s enabled. Returns NULL when failing to get peer address.\n\nzframe_t *\nzsys_udp_recv (SOCKET udpsock, char *peername, int peerlen)\n{\n    char buffer [UDP_FRAME_MAX];\n    in6addr_t address6;\n    socklen_t address_len = sizeof (in6addr_t);\n    ssize_t size = recvfrom (\n        udpsock,\n        buffer, UDP_FRAME_MAX,\n        0,      //  Flags\n        (struct sockaddr *) &address6, &address_len);\n\n    if (size == SOCKET_ERROR)\n        zsys_socket_error ("recvfrom");\n\n    //  Get sender address as printable string\n    int rc = getnameinfo ((struct sockaddr *) &address6, address_len,\n                 peername, peerlen, NULL, 0, NI_NUMERICHOST);\n\n    if (rc) {\n        zsys_warning ("zsys_udp_recv: getnameinfo failed, reason=%s",\n                gai_strerror (rc));\n        return NULL;\n    }\n\n    //  Some platform\'s getnameinfo, like Solaris, appear not to append the\n    //  interface name when parsing a link-local IPv6 address. These addresses\n    //  cannot be used without the interface, so we must append it manually.\n    //  On Windows, if_indextoname is only available from Vista.\n#if !defined (__WINDOWS__) || (_WIN32_WINNT >= 0x0600)\n    if (address6.sin6_family == AF_INET6 &&\n            IN6_IS_ADDR_LINKLOCAL (&address6.sin6_addr) &&\n            !strchr (peername, \'%\')) {\n        char ifname [IF_NAMESIZE] = {0};\n        if_indextoname (address6.sin6_scope_id, ifname);\n        strcat (peername, "%");\n        strcat (peername, ifname);\n    }\n#endif\n\n    return zframe_new (buffer, size);\n}\n\n\n//  --------------------------------------------------------------------------\n//  Handle an I/O error on some socket operation; will report and die on\n//  fatal errors, and continue silently on "try again" errors.\n\nvoid\nzsys_socket_error (const char *reason)\n{\n    bool check_errno;\n\n#if defined (__WINDOWS__)\n    switch (WSAGetLastError ()) {\n        case WSAEINTR:        errno = EINTR;      break;\n        case WSAEBADF:        errno = EBADF;      break;\n        case WSAEWOULDBLOCK:  errno = EAGAIN;     break;\n        case WSAEINPROGRESS:  errno = EAGAIN;     break;\n        case WSAENETDOWN:     errno = ENETDOWN;   break;\n        case WSAECONNRESET:   errno = ECONNRESET; break;\n        case WSAECONNABORTED: errno = EPIPE;      break;\n        case WSAESHUTDOWN:    errno = ECONNRESET; break;\n        case WSAEINVAL:       errno = EPIPE;      break;\n        case WSAEADDRNOTAVAIL: errno = EADDRNOTAVAIL; break;\n        case WSAEADDRINUSE:   errno = EADDRINUSE; break;\n        default:              errno = GetLastError ();\n    }\n#endif\n\n    check_errno = (errno == EAGAIN\n                  ||  errno == ENETDOWN\n                  ||  errno == EHOSTUNREACH\n                  ||  errno == ENETUNREACH\n                  ||  errno == EINTR\n                  ||  errno == EPIPE\n                  ||  errno == ECONNRESET);\n#if defined (ENOPROTOOPT)\n    check_errno = (check_errno ||  errno == ENOPROTOOPT);\n#endif\n#if defined (EHOSTDOWN)\n    check_errno = (check_errno ||  errno == EHOSTDOWN);\n#endif\n#if defined (EOPNOTSUPP)\n    check_errno = (check_errno ||  errno == EOPNOTSUPP);\n#endif\n#if defined (EWOULDBLOCK)\n    check_errno = (check_errno ||  errno == EWOULDBLOCK);\n#endif\n#if defined (EPROTO)\n    check_errno = (check_errno ||  errno == EPROTO);\n#endif\n#if defined (ENONET)\n    check_errno = (check_errno ||  errno == ENONET);\n#endif\n\n    if (check_errno)\n        return;             //  Ignore error and try again\n    else {\n        zsys_error ("(UDP) error \'%s\' on %s", strerror (errno), reason);\n        assert (false);\n    }\n}\n\n\n//  --------------------------------------------------------------------------\n//  Return current host name, for use in public tcp:// endpoints. Caller gets\n//  a freshly allocated string, should free it using zstr_free(). If the host\n//  name is not resolvable, returns NULL.\n\nchar *\nzsys_hostname (void)\n{\n    char hostname [NI_MAXHOST];\n    gethostname (hostname, NI_MAXHOST);\n    hostname [NI_MAXHOST - 1] = 0;\n    struct hostent *host = gethostbyname (hostname);\n\n    if (host && host->h_name)\n        return strdup (host->h_name);\n    else\n        return NULL;\n}\n\n\n//  --------------------------------------------------------------------------\n//  Move the current process into the background. The precise effect depends\n//  on the operating system. On POSIX boxes, moves to a specified working\n//  directory (if specified), closes all file handles, reopens stdin, stdout,\n//  and stderr to the null device, and sets the process to ignore SIGHUP. On\n//  Windows, does nothing. Returns 0 if OK, -1 if there was an error.\n\nint\nzsys_daemonize (const char *workdir)\n{\n#if (defined (__UNIX__))\n    //  Defines umask for new files this process will create\n    mode_t file_mask = 027;     //  Complement of 0750\n\n    //  Recreate our process as a child of init\n    int fork_result = fork ();\n    if (fork_result < 0)        //  < 0 is an error\n        return -1;              //  Could not fork\n    else\n    if (fork_result > 0)        //  > 0 is the parent process\n        exit (0);               //  End parent process\n\n    //  Move to a safe and known directory, which is supplied as an\n    //  argument to this function (or not, if workdir is NULL or empty).\n    if (workdir && zsys_dir_change (workdir)) {\n        zsys_error ("cannot chdir to \'%s\'", workdir);\n        return -1;\n    }\n    //  Close all open file descriptors inherited from the parent\n    //  process, to reduce the resources we use\n    int file_handle = sysconf (_SC_OPEN_MAX);\n    while (file_handle)\n        close (file_handle--);  //  Ignore any errors\n\n    //  Set the umask for new files we might create\n    umask (file_mask);\n\n    //  Set standard input and output to the null device so that any\n    //  code that assumes that these files are open will work\n    file_handle = open ("/dev/null", O_RDWR);\n    int fh_stdout = dup (file_handle);\n    int fh_stderr = dup (file_handle);\n    assert (fh_stdout);\n    assert (fh_stderr);\n\n    //  Ignore any hangup signal from the controlling console\n    signal (SIGHUP, SIG_IGN);\n#endif\n    return 0;\n}\n\n\n//  --------------------------------------------------------------------------\n//  Drop the process ID into the lockfile, with exclusive lock, and switch\n//  the process to the specified group and/or user. Any of the arguments\n//  may be null, indicating a no-op. Returns 0 on success, -1 on failure.\n//  Note if you combine this with zsys_daemonize, run after, not before\n//  that method, or the lockfile will hold the wrong process ID.\n\nint\nzsys_run_as (const char *lockfile, const char *group, const char *user)\n{\n#if (defined (__UNIX__))\n    //  Switch to effective user ID (who owns executable); for\n    //  system services this should be root, so that we can write\n    //  the PID file into e.g. /var/run/\n    if (seteuid (geteuid ())) {\n        zsys_error ("cannot set effective user id: %s", strerror (errno));\n        return -1;\n    }\n    if (lockfile) {\n        //  We enforce a lock on the lockfile, if specified, so that\n        //  only one copy of the process can run at once.\n        int handle = open (lockfile, O_RDWR | O_CREAT, 0640);\n        if (handle < 0) {\n            zsys_error ("cannot open lockfile \'%s\': %s", lockfile, strerror (errno));\n            return -1;\n        }\n        else {\n            struct flock filelock;\n            filelock.l_type = F_WRLCK;      //  F_RDLCK, F_WRLCK, F_UNLCK\n            filelock.l_whence = SEEK_SET;   //  SEEK_SET, SEEK_CUR, SEEK_END\n            filelock.l_start = 0;           //  Offset from l_whence\n            filelock.l_len = 0;             //  length, 0 = to EOF\n            filelock.l_pid = getpid ();\n            if (fcntl (handle, F_SETLK, &filelock)) {\n                zsys_error ("cannot get lock: %s", strerror (errno));\n                return -1;\n            }\n        }\n        //   We record the current process id in the lock file\n        char pid_buffer [32];\n        snprintf (pid_buffer, sizeof (pid_buffer), "%6" PRIi64 "\\n", (int64_t)getpid ());\n        if ((size_t) write (handle, pid_buffer, strlen (pid_buffer)) != strlen (pid_buffer)) {\n            zsys_error ("cannot write to lockfile: %s", strerror (errno));\n            close (handle);\n            return -1;\n        }\n    }\n    if (group) {\n        zsys_info ("running under group \'%s\'", group);\n        struct group *grpbuf = NULL;\n        grpbuf = getgrnam (group);\n        if (grpbuf == NULL || setgid (grpbuf->gr_gid)) {\n            zsys_error ("could not switch group: %s", strerror (errno));\n            return -1;\n        }\n    }\n    if (user) {\n        zsys_info ("running under user \'%s\'", user);\n        struct passwd *pwdbuf = NULL;\n        pwdbuf = getpwnam (user);\n        if (pwdbuf == NULL || setuid (pwdbuf->pw_uid)) {\n            zsys_error ("could not switch user: %s", strerror (errno));\n            return -1;\n        }\n    }\n    else {\n        //  Switch back to real user ID (who started process)\n        if (setuid (getuid ())) {\n            zsys_error ("cannot set real user id: %s", strerror (errno));\n            return -1;\n        }\n    }\n    return 0;\n#else\n    // This is not yet ported to Windows and should not succeed there.\n    return -1;\n#endif\n}\n\n\n//  --------------------------------------------------------------------------\n//  Returns true if the underlying libzmq supports CURVE security.\n//  Uses a heuristic probe according to the version of libzmq being used.\n\nbool\nzsys_has_curve (void)\n{\n#if defined (ZMQ_CURVE_SERVER)\n#   if defined (ZMQ_HAS_CAPABILITIES)\n    //  This is the most modern way of probing libzmq capabilities\n    return zmq_has ("curve") != 0;\n#   else\n    //  However trying the zmq_setsockopt will also work\n    int rc = -1; // assume we fail\n    void *ctx = zmq_ctx_new ();\n    if (ctx) {\n        void *pub = zmq_socket (ctx, ZMQ_PUB);\n        if (pub) {\n            int as_server = 1;\n            rc = zmq_setsockopt (pub, ZMQ_CURVE_SERVER, &as_server, sizeof (int));\n            zmq_close (pub);\n        }\n        zmq_term (ctx);\n    }\n    return rc != -1;\n#   endif\n#else\n    return false;\n#endif\n}\n\n\n//  --------------------------------------------------------------------------\n//  Configure the number of I/O threads that ZeroMQ will use. A good\n//  rule of thumb is one thread per gigabit of traffic in or out. The\n//  default is 1, sufficient for most applications. If the environment\n//  variable ZSYS_IO_THREADS is defined, that provides the default.\n//  Note that this method is valid only before any socket is created.\n\nvoid\nzsys_set_io_threads (size_t io_threads)\n{\n    zsys_init ();\n    ZMUTEX_LOCK (s_mutex);\n    if (s_open_sockets)\n        zsys_error ("zsys_io_threads() is not valid after creating sockets");\n    assert (s_open_sockets == 0);\n\n    s_io_threads = io_threads;\n#if ZMQ_VERSION < ZMQ_MAKE_VERSION(3, 2, 0)\n    zmq_term (s_process_ctx);\n    s_process_ctx = zmq_init ((int) s_io_threads);\n#else\n#  if defined (ZMQ_IO_THREADS)\n    zmq_ctx_set (s_process_ctx, ZMQ_IO_THREADS, s_io_threads);\n#  endif\n#endif\n    ZMUTEX_UNLOCK (s_mutex);\n\n#if ZMQ_VERSION < ZMQ_MAKE_VERSION(3, 2, 0)\n    //  Reinitialised outside of the lock to avoid recursive lock\n    zsys_set_max_msgsz (s_max_msgsz);\n    zsys_set_max_sockets (s_max_sockets);\n#endif\n}\n\n\n//  --------------------------------------------------------------------------\n//  Configure the scheduling policy of the ZMQ context thread pool.\n//  Not available on Windows. See the sched_setscheduler man page or sched.h\n//  for more information. If the environment variable ZSYS_THREAD_SCHED_POLICY\n//  is defined, that provides the default.\n//  Note that this method is valid only before any socket is created.\n\nvoid\nzsys_set_thread_sched_policy (int policy)\n{\n    if (policy < 0)\n        return;\n\n    zsys_init ();\n    ZMUTEX_LOCK (s_mutex);\n    //  If the app is misusing this method, burn it with fire\n    if (s_open_sockets)\n        zsys_error ("zsys_set_thread_sched_policy() is not valid after"\n                " creating sockets");\n    assert (s_open_sockets == 0);\n    s_thread_sched_policy = policy;\n#if defined (ZMQ_THREAD_SCHED_POLICY)\n    zmq_ctx_set (s_process_ctx, ZMQ_THREAD_SCHED_POLICY, s_thread_sched_policy);\n#endif\n    ZMUTEX_UNLOCK (s_mutex);\n}\n\n\n//  --------------------------------------------------------------------------\n//  Configure the numeric prefix to each thread created for the internal\n//  context\'s thread pool. This option is only supported on Linux.\n//  If the environment variable ZSYS_THREAD_NAME_PREFIX is defined, that\n//  provides the default.\n//  Note that this method is valid only before any socket is created.\n\nvoid\nzsys_set_thread_name_prefix (int prefix)\n{\n    if (prefix < 0)\n        return;\n\n    zsys_init ();\n    ZMUTEX_LOCK (s_mutex);\n    //  If the app is misusing this method, burn it with fire\n    if (s_open_sockets)\n        zsys_error ("zsys_set_thread_name_prefix() is not valid after"\n                " creating sockets");\n    assert (s_open_sockets == 0);\n    s_thread_name_prefix = prefix;\n#if defined (ZMQ_THREAD_NAME_PREFIX)\n    zmq_ctx_set (s_process_ctx, ZMQ_THREAD_NAME_PREFIX, s_thread_name_prefix);\n#endif\n    ZMUTEX_UNLOCK (s_mutex);\n}\n\n//  --------------------------------------------------------------------------\n//  Return ZMQ_THREAD_NAME_PREFIX option.\nint\nzsys_thread_name_prefix ()\n{\n    zsys_init ();\n    ZMUTEX_LOCK (s_mutex);\n#if defined (ZMQ_THREAD_NAME_PREFIX)\n    s_thread_name_prefix = zmq_ctx_get (s_process_ctx, ZMQ_THREAD_NAME_PREFIX);\n#endif\n    ZMUTEX_UNLOCK (s_mutex);\n    return s_thread_name_prefix;\n}\n\n\n//  --------------------------------------------------------------------------\n//  Configure the string prefix to each thread created for the internal\n//  context\'s thread pool. This option is only supported on Linux.\n//  If the environment variable ZSYS_THREAD_NAME_PREFIX_STR is defined, that\n//  provides the default.\n//  Note that this method is valid only before any socket is created.\n\nvoid\nzsys_set_thread_name_prefix_str (const char *prefix)\n{\n    size_t prefix_len = 0;\n\n    if (!prefix)\n        return;\n    prefix_len = strlen (prefix);\n    if (prefix_len == 0 || prefix_len > sizeof (s_thread_name_prefix_str) - 1)\n        return;\n\n    zsys_init ();\n    ZMUTEX_LOCK (s_mutex);\n    //  If the app is misusing this method, burn it with fire\n    if (s_open_sockets)\n        zsys_error ("zsys_set_thread_name_prefix() is not valid after"\n                " creating sockets");\n    assert (s_open_sockets == 0);\n    strcpy(s_thread_name_prefix_str, prefix);\n#if defined (ZMQ_THREAD_NAME_PREFIX) && defined (ZMQ_BUILD_DRAFT_API) && \\\n    ((ZMQ_VERSION_MAJOR > 4) || \\\n        ((ZMQ_VERSION_MAJOR >= 4) && ((ZMQ_VERSION_MINOR > 3) || \\\n            ((ZMQ_VERSION_MINOR >= 3) && (ZMQ_VERSION_PATCH >= 3)))))\n    zmq_ctx_set_ext (s_process_ctx, ZMQ_THREAD_NAME_PREFIX, s_thread_name_prefix_str, sizeof (s_thread_name_prefix_str));\n#endif\n    ZMUTEX_UNLOCK (s_mutex);\n}\n\n//  --------------------------------------------------------------------------\n//  Return ZMQ_THREAD_NAME_PREFIX_STR option.\nconst char *\nzsys_thread_name_prefix_str ()\n{\n    size_t prefix_len = sizeof (s_thread_name_prefix_str);\n\n    zsys_init ();\n    ZMUTEX_LOCK (s_mutex);\n#if defined (ZMQ_THREAD_NAME_PREFIX) && defined (ZMQ_BUILD_DRAFT_API) && \\\n    ((ZMQ_VERSION_MAJOR > 4) || \\\n        ((ZMQ_VERSION_MAJOR >= 4) && ((ZMQ_VERSION_MINOR > 3) || \\\n            ((ZMQ_VERSION_MINOR >= 3) && (ZMQ_VERSION_PATCH >= 3)))))\n    zmq_ctx_get_ext (s_process_ctx, ZMQ_THREAD_NAME_PREFIX, s_thread_name_prefix_str, &prefix_len);\n#else\n    (void) prefix_len;\n#endif\n    ZMUTEX_UNLOCK (s_mutex);\n    return s_thread_name_prefix_str;\n}\n\n\n//  --------------------------------------------------------------------------\n//  Adds a specific CPU to the affinity list of the ZMQ context thread pool.\n//  This option is only supported on Linux.\n//  Note that this method is valid only before any socket is created.\n\nvoid\nzsys_thread_affinity_cpu_add (int cpu)\n{\n    if (cpu < 0)\n        return;\n\n    zsys_init ();\n    ZMUTEX_LOCK (s_mutex);\n    //  If the app is misusing this method, burn it with fire\n    if (s_open_sockets)\n        zsys_error ("zsys_set_thread_sched_policy() is not valid after"\n                " creating sockets");\n    assert (s_open_sockets == 0);\n#if defined (ZMQ_THREAD_AFFINITY_CPU_ADD)\n    zmq_ctx_set (s_process_ctx, ZMQ_THREAD_AFFINITY_CPU_ADD, cpu);\n#endif\n    ZMUTEX_UNLOCK (s_mutex);\n}\n\n\n//  --------------------------------------------------------------------------\n//  Removes a specific CPU to the affinity list of the ZMQ context thread pool.\n//  This option is only supported on Linux.\n//  Note that this method is valid only before any socket is created.\n\nvoid\nzsys_thread_affinity_cpu_remove (int cpu)\n{\n    if (cpu < 0)\n        return;\n\n    zsys_init ();\n    ZMUTEX_LOCK (s_mutex);\n    //  If the app is misusing this method, burn it with fire\n    if (s_open_sockets)\n        zsys_error ("zsys_set_thread_sched_policy() is not valid after"\n                " creating sockets");\n    assert (s_open_sockets == 0);\n#if defined (ZMQ_THREAD_AFFINITY_CPU_REMOVE)\n    zmq_ctx_set (s_process_ctx, ZMQ_THREAD_AFFINITY_CPU_REMOVE, cpu);\n#endif\n    ZMUTEX_UNLOCK (s_mutex);\n}\n\n\n//  --------------------------------------------------------------------------\n//  Configure the scheduling priority of the ZMQ context thread pool.\n//  Not available on Windows. See the sched_setscheduler man page or sched.h\n//  for more information. If the environment variable ZSYS_THREAD_PRIORITY is\n//  defined, that provides the default.\n//  Note that this method is valid only before any socket is created.\n\nvoid\nzsys_set_thread_priority (int priority)\n{\n    if (priority < 0)\n        return;\n\n    zsys_init ();\n    ZMUTEX_LOCK (s_mutex);\n    //  If the app is misusing this method, burn it with fire\n    if (s_open_sockets)\n        zsys_error ("zsys_set_thread_priority() is not valid after"\n                " creating sockets");\n    assert (s_open_sockets == 0);\n    s_thread_priority = priority;\n#if defined (ZMQ_THREAD_PRIORITY)\n    zmq_ctx_set (s_process_ctx, ZMQ_THREAD_PRIORITY, s_thread_priority);\n#endif\n    ZMUTEX_UNLOCK (s_mutex);\n}\n\n\n//  --------------------------------------------------------------------------\n//  Configure the number of sockets that ZeroMQ will allow. The default\n//  is 1024. The actual limit depends on the system, and you can query it\n//  by using zsys_socket_limit (). A value of zero means "maximum".\n//  Note that this method is valid only before any socket is created.\n\nvoid\nzsys_set_max_sockets (size_t max_sockets)\n{\n    zsys_init ();\n    ZMUTEX_LOCK (s_mutex);\n    //  If the app is misusing this method, burn it with fire\n    if (s_open_sockets)\n        zsys_error ("zsys_max_sockets() is not valid after creating sockets");\n    assert (s_open_sockets == 0);\n    s_max_sockets = max_sockets? max_sockets: zsys_socket_limit ();\n#if defined (ZMQ_MAX_SOCKETS)\n    zmq_ctx_set (s_process_ctx, ZMQ_MAX_SOCKETS, (int) s_max_sockets);\n#endif\n    ZMUTEX_UNLOCK (s_mutex);\n}\n\n\n//  --------------------------------------------------------------------------\n//  Return maximum number of ZeroMQ sockets that the system will support.\n\nsize_t\nzsys_socket_limit (void)\n{\n    size_t socket_limit;\n#if defined (ZMQ_SOCKET_LIMIT)\n    if (s_process_ctx)\n        socket_limit = (size_t) zmq_ctx_get (s_process_ctx, ZMQ_SOCKET_LIMIT);\n    else {\n        void *ctx = zmq_init (1);\n        socket_limit = (size_t) zmq_ctx_get (ctx, ZMQ_SOCKET_LIMIT);\n        zmq_term (ctx);\n    }\n    //  ZeroMQ used to report a nonsense value (2^31) which if used would\n    //  on zmq_ctx_set (ZMQ_MAX_SOCKETS) cause an out-of-memory error. So\n    //  if we\'re running on an older library, enforce a sane limit.\n    if (socket_limit > 65535)\n        socket_limit = 65535;\n#else\n    socket_limit = 1024;\n#endif\n    return socket_limit;\n}\n\n\n//  --------------------------------------------------------------------------\n//  Configure the maximum allowed size of a message sent.\n//  The default is INT_MAX.\n\nvoid\nzsys_set_max_msgsz (int max_msgsz)\n{\n    if (max_msgsz < 0)\n        return;\n\n    zsys_init ();\n    ZMUTEX_LOCK (s_mutex);\n    s_max_msgsz = max_msgsz;\n#if defined (ZMQ_MAX_MSGSZ)\n    zmq_ctx_set (s_process_ctx, ZMQ_MAX_MSGSZ, (int) s_max_msgsz);\n#endif\n    ZMUTEX_UNLOCK (s_mutex);\n}\n\n//  --------------------------------------------------------------------------\n//  Configure whether to use zero copy strategy in libzmq. If the environment\n//  variable ZSYS_ZERO_COPY_RECV is defined, that provides the default.\n//  Otherwise the default is 1.\n\nvoid\nzsys_set_zero_copy_recv(int zero_copy)\n{\n    zsys_init ();\n    ZMUTEX_LOCK (s_mutex);\n    s_zero_copy_recv = zero_copy;\n#if defined (ZMQ_ZERO_COPY_RECV)\n    zmq_ctx_set (s_process_ctx, ZMQ_ZERO_COPY_RECV, s_zero_copy_recv);\n#endif\n    ZMUTEX_UNLOCK (s_mutex);\n}\n\n//  --------------------------------------------------------------------------\n//  Return ZMQ_ZERO_COPY_RECV option.\nint\nzsys_zero_copy_recv()\n{\n    zsys_init ();\n    ZMUTEX_LOCK (s_mutex);\n#if defined (ZMQ_ZERO_COPY_RECV)\n    s_zero_copy_recv = zmq_ctx_get (s_process_ctx, ZMQ_ZERO_COPY_RECV);\n#endif\n    ZMUTEX_UNLOCK (s_mutex);\n    return s_zero_copy_recv;\n}\n\n\n//  --------------------------------------------------------------------------\n//  Return maximum message size.\n\nint\nzsys_max_msgsz (void)\n{\n    zsys_init ();\n    ZMUTEX_LOCK (s_mutex);\n#if defined (ZMQ_MAX_MSGSZ)\n    s_max_msgsz = zmq_ctx_get (s_process_ctx, ZMQ_MAX_MSGSZ);\n#endif\n    ZMUTEX_UNLOCK (s_mutex);\n    return s_max_msgsz;\n}\n\n\n//  --------------------------------------------------------------------------\n//  *** Draft method, for development use, may change without warning ***\n//  Check if default interrupt handler of Ctrl-C or SIGTERM was called.\n//  Does not work if ZSYS_SIGHANDLER is false and code does not call\n//  set interrupted on signal.\nbool\n    zsys_is_interrupted (void)\n{\n    return zsys_interrupted != 0;\n}\n\n//  --------------------------------------------------------------------------\n//  *** Draft method, for development use, may change without warning ***\n//  Set interrupted flag. This is done by default signal handler, however\n//  this can be handy for language bindings or cases without default\n//  signal handler.\nvoid\n    zsys_set_interrupted (void)\n{\n    zctx_interrupted = 1;\n    zsys_interrupted = 1;\n}\n\n//  --------------------------------------------------------------------------\n//  Configure the threshold value of filesystem object age per st_mtime\n//  that should elapse until we consider that object "stable" at the\n//  current zclock_time() moment.\n//  The default is S_DEFAULT_ZSYS_FILE_STABLE_AGE_MSEC defined in zsys.c\n//  which generally depends on host OS, with fallback value of 5000.\n\nvoid\n    zsys_set_file_stable_age_msec (int64_t file_stable_age_msec)\n{\n    if (file_stable_age_msec < 1)\n        return;\n\n    zsys_init ();\n    ZMUTEX_LOCK (s_mutex);\n    s_file_stable_age_msec = file_stable_age_msec;\n    ZMUTEX_UNLOCK (s_mutex);\n}\n\n\n//  --------------------------------------------------------------------------\n//  Return current threshold value of file stable age in msec.\n//  This can be used in code that chooses to wait for this timeout\n//  before testing if a filesystem object is "stable" or not.\n\n//  Note that the OS timer quantization can bite you, so it may be\n//  reasonably safe to sleep/wait/poll for a larger timeout before\n//  assuming a fault, e.g. the default timer resolution on Windows\n//  is 15.6 ms (per timer interrupt 64 times a second), graphed here:\n//    https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx\n//  and Unix/Linux OSes also have different-resolution timers.\n\nint64_t\n    zsys_file_stable_age_msec (void)\n{\n    zsys_init ();\n    return s_file_stable_age_msec;\n}\n\n\n//  --------------------------------------------------------------------------\n//  Configure the default linger timeout in msecs for new zsock instances.\n//  You can also set this separately on each zsock_t instance. The default\n//  linger time is zero, i.e. any pending messages will be dropped. If the\n//  environment variable ZSYS_LINGER is defined, that provides the default.\n//  Note that process exit will typically be delayed by the linger time.\n\nvoid\nzsys_set_linger (size_t linger)\n{\n    zsys_init ();\n    ZMUTEX_LOCK (s_mutex);\n    s_linger = linger;\n    ZMUTEX_UNLOCK (s_mutex);\n}\n\n\n//  --------------------------------------------------------------------------\n//  Configure the default outgoing pipe limit (HWM) for new zsock instances.\n//  You can also set this separately on each zsock_t instance. The default\n//  HWM is 1,000, on all versions of ZeroMQ. If the environment variable\n//  ZSYS_SNDHWM is defined, that provides the default. Note that a value of\n//  zero means no limit, i.e. infinite memory consumption.\n\nvoid\nzsys_set_sndhwm (size_t sndhwm)\n{\n    zsys_init ();\n    ZMUTEX_LOCK (s_mutex);\n    s_sndhwm = sndhwm;\n    ZMUTEX_UNLOCK (s_mutex);\n}\n\n\n//  --------------------------------------------------------------------------\n//  Configure the default incoming pipe limit (HWM) for new zsock instances.\n//  You can also set this separately on each zsock_t instance. The default\n//  HWM is 1,000, on all versions of ZeroMQ. If the environment variable\n//  ZSYS_RCVHWM is defined, that provides the default. Note that a value of\n//  zero means no limit, i.e. infinite memory consumption.\n\nvoid\nzsys_set_rcvhwm (size_t rcvhwm)\n{\n    zsys_init ();\n    ZMUTEX_LOCK (s_mutex);\n    s_rcvhwm = rcvhwm;\n    ZMUTEX_UNLOCK (s_mutex);\n}\n\n\n//  --------------------------------------------------------------------------\n//  Configure the default HWM for zactor internal pipes; this is set on both\n//  ends of the pipe, for outgoing messages only (sndhwm). The default HWM is\n//  1,000, on all versions of ZeroMQ. If the environment var ZSYS_ACTORHWM is\n//  defined, that provides the default. Note that a value of zero means no\n//  limit, i.e. infinite memory consumption.\n\nvoid\nzsys_set_pipehwm (size_t pipehwm)\n{\n    zsys_init ();\n    ZMUTEX_LOCK (s_mutex);\n    s_pipehwm = pipehwm;\n    ZMUTEX_UNLOCK (s_mutex);\n}\n\n\n//  --------------------------------------------------------------------------\n//  Return the HWM for zactor internal pipes.\n\nsize_t\nzsys_pipehwm (void)\n{\n    return s_pipehwm;\n}\n\n\n//  --------------------------------------------------------------------------\n//  Configure use of IPv6 for new zsock instances. By default sockets accept\n//  and make only IPv4 connections. When you enable IPv6, sockets will accept\n//  and connect to both IPv4 and IPv6 peers. You can override the setting on\n//  each zsock_t instance. The default is IPv4 only (ipv6 set to 0). If the\n//  environment variable ZSYS_IPV6 is defined (as 1 or 0), this provides the\n//  default. Note: has no effect on ZMQ v2.\n\nvoid\nzsys_set_ipv6 (int ipv6)\n{\n    zsys_init ();\n    ZMUTEX_LOCK (s_mutex);\n    s_ipv6 = ipv6;\n    ZMUTEX_UNLOCK (s_mutex);\n}\n\n\n//  --------------------------------------------------------------------------\n//  Return use of IPv6 for zsock instances.\n\nint\nzsys_ipv6 (void)\n{\n    return s_ipv6;\n}\n\n//  Test if ipv6 is available on the system. The only way to reliably\n//  check is to actually open a socket and try to bind it. (ported from\n//  libzmq)\n\nbool\nzsys_ipv6_available (void)\n{\n#if defined(__WINDOWS__) && (_WIN32_WINNT < 0x0600)\n    return 0;\n#else\n    int rc, ipv6 = 1;\n    struct sockaddr_in6 test_addr;\n\n    memset (&test_addr, 0, sizeof (test_addr));\n    test_addr.sin6_family = AF_INET6;\n    inet_pton (AF_INET6, "::1", &(test_addr.sin6_addr));\n\n    SOCKET fd = socket (AF_INET6, SOCK_STREAM, IPPROTO_IP);\n    if (fd == INVALID_SOCKET)\n        ipv6 = 0;\n    else {\n#if defined(__WINDOWS__)\n        setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (const char *) &ipv6,\n                    sizeof (int));\n        rc = setsockopt (fd, IPPROTO_IPV6, IPV6_V6ONLY, (const char *) &ipv6,\n                         sizeof (int));\n        if (rc == SOCKET_ERROR)\n            ipv6 = 0;\n        else {\n            rc = bind (fd, (struct sockaddr *) &test_addr, sizeof (test_addr));\n            if (rc == SOCKET_ERROR)\n                ipv6 = 0;\n        }\n        closesocket (fd);\n#else\n        setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &ipv6, sizeof (int));\n        rc = setsockopt (fd, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6, sizeof (int));\n        if (rc != 0)\n            ipv6 = 0;\n        else {\n            rc = bind (fd, (struct sockaddr *) (&test_addr),\n                       sizeof (test_addr));\n            if (rc != 0)\n                ipv6 = 0;\n        }\n        close (fd);\n#endif\n    }\n\n    return ipv6;\n#endif // _WIN32_WINNT < 0x0600\n}\n\n//  --------------------------------------------------------------------------\n//  Set network interface name to use for broadcasts, particularly zbeacon.\n//  This lets the interface be configured for test environments where required.\n//  For example, on Mac OS X, zbeacon cannot bind to 255.255.255.255 which is\n//  the default when there is no specified interface. If the environment\n//  variable ZSYS_INTERFACE is set, use that as the default interface name.\n//  Setting the interface to "*" means "use all interfaces".\n\nvoid\nzsys_set_interface (const char *value)\n{\n    zsys_init ();\n    freen (s_interface);\n    s_interface = strdup (value);\n    assert (s_interface);\n}\n\n\n//  --------------------------------------------------------------------------\n//  Return network interface to use for broadcasts, or "" if none was set.\n\nconst char *\nzsys_interface (void)\n{\n    return s_interface? s_interface: "";\n}\n\n\n//  --------------------------------------------------------------------------\n//  Set IPv6 address to use zbeacon socket, particularly for receiving zbeacon.\n//  This needs to be set IPv6 is enabled as IPv6 can have multiple addresses\n//  on a given interface. If the environment variable ZSYS_IPV6_ADDRESS is set,\n//  use that as the default IPv6 address.\n\nvoid\nzsys_set_ipv6_address (const char *value)\n{\n    zsys_init ();\n    freen (s_ipv6_address);\n    s_ipv6_address = strdup (value);\n    assert (s_ipv6_address);\n}\n\n\n//  --------------------------------------------------------------------------\n//  Return IPv6 address to use for zbeacon reception, or "" if none was set.\n\nconst char *\nzsys_ipv6_address (void)\n{\n    return s_ipv6_address? s_ipv6_address: "";\n}\n\n\n//  --------------------------------------------------------------------------\n//  Set IPv6 multicast address to use for sending zbeacon messages. The default\n//  is fe02::1 (link-local all-node). If the environment variable\n//  ZSYS_IPV6_MCAST_ADDRESS is set, use that as the default IPv6 multicast\n//  address.\n\nvoid\nzsys_set_ipv6_mcast_address (const char *value)\n{\n    zsys_init ();\n    freen (s_ipv6_mcast_address);\n    s_ipv6_mcast_address = strdup (value);\n    assert (s_ipv6_mcast_address);\n}\n\n//  --------------------------------------------------------------------------\n//  Set IPv4 multicast address to use for sending zbeacon messages. By default\n//  IPv4 multicast is NOT used. If the environment variable\n//  ZSYS_IPV4_MCAST_ADDRESS is set, use that as the default IPv4 multicast\n//  address. Calling this function or setting ZSYS_IPV4_MCAST_ADDRESS\n//  will enable IPv4 zbeacon messages.\n\nvoid zsys_set_ipv4_mcast_address (const char *value)\n{\n    zsys_init ();\n    freen (s_ipv4_mcast_address);\n    s_ipv4_mcast_address = value ? strdup (value) : NULL;\n    assert (!value || s_ipv4_mcast_address);\n}\n\n//  --------------------------------------------------------------------------\n//  Set multicast TTL\n\nvoid zsys_set_mcast_ttl (const unsigned char value)\n{\n    zsys_init ();\n    s_mcast_ttl = value;\n    assert (s_mcast_ttl);\n}\n\n\n//  --------------------------------------------------------------------------\n//  Return IPv6 multicast address to use for sending zbeacon, or\n//  "ff02:0:0:0:0:0:0:1" if none was set.\n\nconst char *\nzsys_ipv6_mcast_address (void)\n{\n    return s_ipv6_mcast_address ? s_ipv6_mcast_address : "ff02:0:0:0:0:0:0:1";\n}\n\n//  --------------------------------------------------------------------------\n//  Return IPv4 multicast address to use for sending zbeacon, or\n//  NULL if none was set.\n\nconst char *zsys_ipv4_mcast_address (void)\n{\n    return s_ipv4_mcast_address;\n}\n\n//  --------------------------------------------------------------------------\n//  Get multicast TTL\n\nunsigned char zsys_mcast_ttl ()\n{\n    return s_mcast_ttl;\n}\n\n\n//  --------------------------------------------------------------------------\n//  Configure the automatic use of pre-allocated FDs when creating new sockets.\n//  If 0 (default), nothing will happen. Else, when a new socket is bound, the\n//  system API will be used to check if an existing pre-allocated FD with a\n//  matching port (if TCP) or path (if IPC) exists, and if it does it will be\n//  set via the ZMQ_USE_FD socket option so that the library will use it\n//  instead of creating a new socket.\n\n\nvoid\nzsys_set_auto_use_fd (int auto_use_fd)\n{\n    zsys_init ();\n    ZMUTEX_LOCK (s_mutex);\n    s_auto_use_fd = auto_use_fd;\n    ZMUTEX_UNLOCK (s_mutex);\n}\n\n\n//  --------------------------------------------------------------------------\n//  Return use of automatic pre-allocated FDs for zsock instances.\n\nint\nzsys_auto_use_fd (void)\n{\n    return s_auto_use_fd;\n}\n\ntypedef enum _zsprintf_s {\n    FIND_PERCENT,\n    FIND_KEY,\n    FIND_FORMAT,\n    END\n} zsprintf_s;\n\ntypedef void* (*zsys_lookup_fn)(void *, const char*);\n\nstatic char *\ns_zsys_zprintf (const char *format, void *args, zsys_lookup_fn lookup_fn, bool validate)\n{\n    assert (format);\n    assert (args);\n    zchunk_t *chunk = zchunk_new (NULL, strlen (format) * 1.5);\n    assert (chunk);\n    char *ret = NULL;\n\n    zsprintf_s state = FIND_PERCENT;\n    size_t pos = 0;\n    char *key = NULL;\n\n    while (state != END)\n    {\n\n        if (pos >= strlen (format))\n            break;\n\n        switch (state) {\n            case FIND_PERCENT:\n                {\n\n                    //zsys_debug ("\\tstate=FIND_PERCENT, format+%zu=%s", pos, format+pos);\n                    char *percent = strchr ((char*)(format) + pos, \'%\');\n\n                    if (!percent) {\n                        //zsys_debug ("!percent");\n                        zchunk_extend (chunk, (format+pos), strlen (format) - pos);\n                        state = END;\n                    }\n                    else\n                    if (*(percent+1) == \'%\') {\n                        size_t idx = percent - format;\n                        //zsys_debug ("*(percent+1)==\'%%\':\\tidx=%zu, format+%zu=%s", idx, pos, format+pos);\n                        if (idx - pos > 0) {\n                            //zsys_debug ("*(percent+1)==\'%%\':\\t#2pos=%zu, idx-pos=%zu", pos, idx-pos);\n                            zchunk_extend (chunk, format+pos, idx - pos);\n                            pos += (idx-pos);\n                            //zsys_debug ("*(percent+1)==\'%%\':\\t#2pos=%zu, idx-pos=%zu", pos, idx-pos);\n                        }\n                        zchunk_extend (chunk, "%", 1);\n                        pos += 2;\n                    }\n                    else\n                    if (*(percent+1) == \'(\') {\n                        size_t idx = percent - format;\n                        //zsys_debug ("*(percent+1) == \'(\': idx=%zu, pos=%zu", idx, pos);\n                        if (idx - pos > 0) {\n                            zchunk_extend (chunk, (format+pos), idx - pos);\n                            pos += (idx-pos);\n                        }\n                        //zsys_debug ("*(percent+1) == \'(\': idx=%zu, pos=%zu", idx, pos);\n                        state = FIND_KEY;\n                    }\n                    else {\n                        //zsys_debug ("else");\n                        size_t idx = percent - format;\n                        zchunk_extend (chunk, (format+pos), idx - pos);\n                        pos += (idx-pos);\n                    }\n                }\n                break;\n            case FIND_KEY:\n                {\n                    //zsys_debug ("\\tstate=FIND_KEY, format+%zu=%s", pos, format+pos);\n                    char *key_end = strchr ((char*)(format)+pos, \')\');\n                    if (!key_end) {\n                        zchunk_extend (chunk, (format+pos), strlen (format) - pos);\n                        state = END;\n                    }\n                    pos += 2;\n                    size_t idx = key_end - format;\n                    size_t key_len = idx - pos;\n                    if (key_len == 0) {\n                        zchunk_extend (chunk, "()", 2);\n                        pos += 2;\n                        state = FIND_PERCENT;\n                    }\n                    zstr_free (&key);\n                    key = (char*) zmalloc (key_len + 1);\n                    memcpy ((void*) key, format+pos, key_len);\n\n                    if (! lookup_fn (args, key)) {\n                        char *ret = NULL;\n                        if (validate)\n                            ret = zsys_sprintf ("Key \'%s\' not found in hash", key);\n                        zstr_free (&key);\n                        zchunk_destroy (&chunk);\n                        return ret;\n                    }\n                    pos += key_len + 1;\n                    state = FIND_FORMAT;\n                }\n                break;\n            case FIND_FORMAT:\n                {\n                    //zsys_debug ("\\tstate=FIND_FORMAT, format+%zu=%s", pos, format+pos);\n                    if (*(format+pos) != \'s\') {\n                        char *ret = NULL;\n                        if (validate)\n                            ret = zsys_sprintf ("%s: arguments other than \'s\' are not implemented", key);\n                        zstr_free (&key);\n                        zchunk_destroy (&chunk);\n                        return ret;\n                    }\n                    pos += 1;\n                    char *v = (char *) lookup_fn (args, key);\n                    zchunk_extend (chunk, v, strlen (v));\n                    state = FIND_PERCENT;\n                }\n                break;\n            case END:\n                break;\n        }\n    }\n    zstr_free (&key);\n\n    //FIXME: is it needed?\n    zchunk_extend (chunk, "\\0", 1);\n\n    if (!validate) {\n        ret = strdup ((char*) zchunk_data (chunk));\n        zchunk_destroy (&chunk);\n        return ret;\n    }\n\n    zchunk_destroy (&chunk);\n    return NULL;\n}\n\n\n// printf based on zhash_t\nchar *\nzsys_zprintf (const char *format, zhash_t *args)\n{\n    return s_zsys_zprintf (format, (void*) args, (zsys_lookup_fn) zhash_lookup, false);\n}\n\n// return missing key or other format errors as new allocated string or NULL\nchar *\nzsys_zprintf_error (const char *format, zhash_t *args)\n{\n    return s_zsys_zprintf (format, (void*) args, (zsys_lookup_fn) zhash_lookup, true);\n}\n\nstatic void *\ns_zconfig_lookup (void *container, const char *key)\n{\n    zconfig_t *root = (zconfig_t*) container;\n    zconfig_t *child = zconfig_locate (root, key);\n    if (child)\n        return zconfig_value (child);\n    return NULL;\n}\n\n// printf based on zconfig\nchar *\nzsys_zplprintf (const char *format, zconfig_t *args)\n{\n    return s_zsys_zprintf (format, (void*) args, (zsys_lookup_fn) s_zconfig_lookup, false);\n}\n\n// return missing key or other format errors as new allocated string or NULL\nchar *\nzsys_zplprintf_error (const char *format, zconfig_t *args)\n{\n    return s_zsys_zprintf (format, (void*) args, (zsys_lookup_fn) s_zconfig_lookup, true);\n}\n\n\n//  --------------------------------------------------------------------------\n//  Set log identity, which is a string that prefixes all log messages sent\n//  by this process. The log identity defaults to the environment variable\n//  ZSYS_LOGIDENT, if that is set.\n\nvoid\nzsys_set_logident (const char *value)\n{\n    zsys_init ();\n    freen (s_logident);\n    s_logident = strdup (value);\n#if defined (__UNIX__)\n    if (s_logsystem)\n        openlog (s_logident, LOG_PID, LOG_USER);\n#elif defined (__WINDOWS__)\n    //  TODO: hook in Windows event log for Windows\n#endif\n    assert (s_logident);\n}\n\n\n//  --------------------------------------------------------------------------\n//  Set stream to receive log traffic. By default, log traffic is sent to\n//  stdout. If you set the stream to NULL, no stream will receive the log\n//  traffic (it may still be sent to the system facility).\n\nvoid\nzsys_set_logstream (FILE *stream)\n{\n    zsys_init ();\n    s_logstream = stream;\n}\n\n\n//  --------------------------------------------------------------------------\n//  Sends log output to a PUB socket bound to the specified endpoint. To\n//  collect such log output, create a SUB socket, subscribe to the traffic\n//  you care about, and connect to the endpoint. Log traffic is sent as a\n//  single string frame, in the same format as when sent to stdout. The\n//  log system supports a single sender; multiple calls to this method will\n//  bind the same sender to multiple endpoints. To disable the sender, call\n//  this method with a null argument.\n\nvoid\nzsys_set_logsender (const char *endpoint)\n{\n    zsys_init ();\n    if (endpoint) {\n        //  Create log sender if needed\n        if (!s_logsender) {\n            s_logsender = zsock_new_pub(NULL);\n            assert (s_logsender);\n        }\n        //  Bind/connect to specified endpoint(s) using zsock_attach() syntax\n        int rc = zsock_attach (s_logsender, endpoint, true);\n        assert (rc == 0);\n    }\n    else\n    if (s_logsender) {\n        zsock_destroy (&s_logsender);\n    }\n}\n\n\n//  --------------------------------------------------------------------------\n//  Enable or disable logging to the system facility (syslog on POSIX boxes,\n//  event log on Windows). By default this is disabled.\n\nvoid\nzsys_set_logsystem (bool logsystem)\n{\n    zsys_init ();\n    s_logsystem = logsystem;\n#if defined (__UNIX__)\n    if (s_logsystem)\n        openlog (s_logident, LOG_PID, LOG_USER);\n#elif defined (__WINDOWS__)\n    //  TODO: hook into Windows event log\n#endif\n}\n\n\nstatic void\ns_log (char loglevel, char *string)\n{\n    if (!s_initialized)\n        zsys_init ();\n\n#if defined (__UNIX__)\n#   if defined (__UTYPE_ANDROID)\n    int priority = ANDROID_LOG_INFO;\n    if (loglevel == \'E\')\n        priority = ANDROID_LOG_ERROR;\n    else\n    if (loglevel == \'W\')\n        priority = ANDROID_LOG_WARN;\n    else\n    if (loglevel == \'N\')\n        priority = ANDROID_LOG_INFO;\n    else\n    if (loglevel == \'I\')\n        priority = ANDROID_LOG_INFO;\n    else\n    if (loglevel == \'D\')\n        priority = ANDROID_LOG_DEBUG;\n\n    __android_log_print(priority, "zsys", "%s", string);\n#   else\n    if (s_logsystem) {\n        int priority = LOG_INFO;\n        if (loglevel == \'E\')\n            priority = LOG_ERR;\n        else\n        if (loglevel == \'W\')\n            priority = LOG_WARNING;\n        else\n        if (loglevel == \'N\')\n            priority = LOG_NOTICE;\n        else\n        if (loglevel == \'I\')\n            priority = LOG_INFO;\n        else\n        if (loglevel == \'D\')\n            priority = LOG_DEBUG;\n\n        syslog (priority, "%s", string);\n    }\n    else\n#   endif\n#endif\n    if (s_logstream || s_logsender) {\n        time_t curtime = time (NULL);\n        struct tm *loctime = localtime (&curtime);\n        char date [20];\n        strftime (date, 20, "%y-%m-%d %H:%M:%S", loctime);\n        char log_text [1024];\n        if (s_logident)\n            snprintf (log_text, 1024, "%c: (%s) %s %s", loglevel, s_logident, date, string);\n        else\n            snprintf (log_text, 1024, "%c: %s %s", loglevel, date, string);\n\n        if (s_logstream) {\n            fprintf (s_logstream, "%s\\n", log_text);\n            fflush (s_logstream);\n        }\n        if (s_logsender)\n            zstr_send (s_logsender, log_text);\n    }\n}\n\n\n//  --------------------------------------------------------------------------\n//  Log error condition - highest priority\n\nvoid\nzsys_error (const char *format, ...)\n{\n    va_list argptr;\n    va_start (argptr, format);\n    char *string = zsys_vprintf (format, argptr);\n    va_end (argptr);\n    s_log (\'E\', string);\n    zstr_free (&string);\n}\n\n\n//  --------------------------------------------------------------------------\n//  Log warning condition - high priority\n\nvoid\nzsys_warning (const char *format, ...)\n{\n    va_list argptr;\n    va_start (argptr, format);\n    char *string = zsys_vprintf (format, argptr);\n    va_end (argptr);\n    s_log (\'W\', string);\n    zstr_free (&string);\n}\n\n\n//  --------------------------------------------------------------------------\n//  Log normal, but significant, condition - normal priority\n\nvoid\nzsys_notice (const char *format, ...)\n{\n    va_list argptr;\n    va_start (argptr, format);\n    char *string = zsys_vprintf (format, argptr);\n    va_end (argptr);\n    s_log (\'N\', string);\n    zstr_free (&string);\n}\n\n\n//  --------------------------------------------------------------------------\n//  Log informational message - low priority\n\nvoid\nzsys_info (const char *format, ...)\n{\n    va_list argptr;\n    va_start (argptr, format);\n    char *string = zsys_vprintf (format, argptr);\n    va_end (argptr);\n    s_log (\'I\', string);\n    zstr_free (&string);\n}\n\n\n//  --------------------------------------------------------------------------\n//  Log debug-level message - lowest priority\n\nvoid\nzsys_debug (const char *format, ...)\n{\n    va_list argptr;\n    va_start (argptr, format);\n    char *string = zsys_vprintf (format, argptr);\n    va_end (argptr);\n    s_log (\'D\', string);\n    zstr_free (&string);\n}\n\n//  --------------------------------------------------------------------------\n//  Selftest\n\nvoid\nzsys_test (bool verbose)\n{\n    printf (" * zsys: ");\n    if (verbose)\n        printf ("\\n");\n\n    // check that we can stop/restart the environnemnt\n    zsys_shutdown();\n    zsys_init();\n    zsys_shutdown();\n    zsys_init();\n#ifdef CZMQ_BUILD_DRAFT_API\n    // just check if we can check for ipv6\n    zsys_ipv6_available();\n#endif\n\n    //  @selftest\n    zsys_catch_interrupts ();\n\n    //  Check capabilities without using the return value\n    int rc = zsys_has_curve ();\n\n    const char *SELFTEST_DIR_RW = "src/selftest-rw";\n\n    if (verbose) {\n        char *hostname = zsys_hostname ();\n        zsys_info ("host name is %s", hostname);\n        freen (hostname);\n        zsys_info ("system limit is %zu ZeroMQ sockets", zsys_socket_limit ());\n    }\n#ifdef CZMQ_BUILD_DRAFT_API\n    zsys_set_file_stable_age_msec (5123);\n    assert (zsys_file_stable_age_msec() == 5123);\n    zsys_set_file_stable_age_msec (-1);\n    assert (zsys_file_stable_age_msec() == 5123);\n#endif // CZMQ_BUILD_DRAFT_API\n    zsys_set_linger (0);\n    zsys_set_sndhwm (1000);\n    zsys_set_rcvhwm (1000);\n    zsys_set_pipehwm (2500);\n    assert (zsys_pipehwm () == 2500);\n    zsys_set_ipv6 (0);\n    zsys_set_thread_priority (-1);\n    zsys_set_thread_sched_policy (-1);\n    zsys_set_thread_name_prefix (0);\n    assert (0 == zsys_thread_name_prefix());\n    assert (streq ("0", zsys_thread_name_prefix_str()));\n    zsys_thread_affinity_cpu_add (0);\n    zsys_thread_affinity_cpu_remove (0);\n    zsys_set_zero_copy_recv(0);\n    assert (0 == zsys_zero_copy_recv());\n    zsys_set_zero_copy_recv(1);\n    assert (1 == zsys_zero_copy_recv());\n\n    //  Test pipe creation\n    zsock_t *pipe_back;\n    zsock_t *pipe_front = zsys_create_pipe (&pipe_back);\n    zstr_send (pipe_front, "Hello");\n    char *string = zstr_recv (pipe_back);\n    assert (streq (string, "Hello"));\n    freen (string);\n    zsock_destroy (&pipe_back);\n    zsock_destroy (&pipe_front);\n\n    //  Test file manipulation\n\n    // Don\'t let anyone fool our workspace\n    if (zsys_file_exists ("nosuchfile")) {\n        zsys_warning ("zsys_test() had to remove \'nosuchfile\' which was not expected here at all");\n        zsys_file_delete ("nosuchfile");\n    }\n\n    rc = zsys_file_delete ("nosuchfile");\n    assert (rc == -1);\n\n    bool rc_bool = zsys_file_exists ("nosuchfile");\n    assert (rc_bool != true);\n\n    rc = (int) zsys_file_size ("nosuchfile");\n    assert (rc == -1);\n\n    time_t when = zsys_file_modified (".");\n    assert (when > 0);\n\n    int mode = zsys_file_mode (".");\n    assert (S_ISDIR (mode));\n    assert (mode & S_IRUSR);\n    assert (mode & S_IWUSR);\n\n    const char *testbasedir  = ".testsys";\n    const char *testsubdir  = "subdir";\n    char *basedirpath = NULL;   // subdir in a test, under SELFTEST_DIR_RW\n    char *dirpath = NULL;       // subdir in a test, under basedirpath\n    char *relsubdir = NULL;     // relative short "path" of subdir under testbasedir\n\n    basedirpath = zsys_sprintf ("%s/%s", SELFTEST_DIR_RW, testbasedir);\n    assert (basedirpath);\n    dirpath = zsys_sprintf ("%s/%s", basedirpath, testsubdir);\n    assert (dirpath);\n    relsubdir = zsys_sprintf ("%s/%s", testbasedir, testsubdir);\n    assert (relsubdir);\n\n    // Normally tests clean up in the end, but if a selftest run dies\n    // e.g. on assert(), workspace remains dirty. Better clean it up.\n    // We do not really care about results here - we clean up a possible\n    // dirty exit of an older build. If there are permission errors etc.\n    // the actual tests below would explode.\n    if (zsys_file_exists(dirpath)) {\n        if (verbose)\n            zsys_debug ("zsys_test() has to remove ./%s that should not have been here", dirpath);\n        zsys_dir_delete (dirpath);\n    }\n    if (zsys_file_exists (basedirpath)) {\n        if (verbose)\n            zsys_debug ("zsys_test() has to remove ./%s that should not have been here", basedirpath);\n        zsys_dir_delete (basedirpath);\n    }\n\n    // Added tracing because this file-age check fails on some systems\n    // presumably due to congestion in a mass-build and valgrind on top\n    zsys_file_mode_private ();\n    if (verbose)\n        printf ("zsys_test() at timestamp %" PRIi64 ": "\n            "Creating %s\\n",\n            zclock_time(), relsubdir );\n    rc = zsys_dir_create ("%s/%s", SELFTEST_DIR_RW, relsubdir);\n    if (verbose)\n        printf ("zsys_test() at timestamp %" PRIi64 ": "\n            "Finished creating %s with return-code %d\\n",\n            zclock_time(), relsubdir, rc );\n    assert (rc == 0);\n    when = zsys_file_modified (dirpath);\n    if (verbose)\n        printf ("zsys_test() at timestamp %" PRIi64 ": "\n            "Finished calling zsys_file_modified(), got age %jd\\n",\n            zclock_time(), (intmax_t)when );\n    assert (when > 0);\n    if (verbose)\n        printf ("zsys_test() at timestamp %" PRIi64 ": "\n            "Checking if file is NOT stable (is younger than 1 sec)\\n",\n            zclock_time() );\n    assert (!s_zsys_file_stable (dirpath, verbose));\n    if (verbose)\n        printf ("zsys_test() at timestamp %" PRIi64 ": "\n            "Passed the test, file is not stable - as expected\\n",\n            zclock_time() );\n    rc = zsys_dir_delete ("%s/%s", SELFTEST_DIR_RW, relsubdir);\n    assert (rc == 0);\n    rc = zsys_dir_delete ("%s/%s", SELFTEST_DIR_RW, testbasedir);\n    assert (rc == 0);\n    zsys_file_mode_default ();\n\n#if (defined (PATH_MAX))\n    char cwd[PATH_MAX];\n#else\n# if (defined (_MAX_PATH))\n    char cwd[_MAX_PATH];\n# else\n    char cwd[1024];\n# endif\n#endif\n    memset (cwd, 0, sizeof(cwd));\n#if (defined (WIN32))\n    if (_getcwd(cwd, sizeof(cwd)) != NULL) {\n#else\n    if (getcwd(cwd, sizeof(cwd)) != NULL) {\n#endif\n        if (verbose)\n            printf ("zsys_test() at timestamp %" PRIi64 ": "\n                "current working directory is %s\\n",\n                zclock_time(), cwd);\n        assert (zsys_dir_change (SELFTEST_DIR_RW) == 0);\n        assert (zsys_dir_change (cwd) == 0);\n    }\n    else {\n        zsys_warning ("zsys_test() : got getcwd() error... "\n            "testing zsys_dir_change() anyway, but it can confuse "\n            "subsequent tests in this process");\n        assert (zsys_dir_change (SELFTEST_DIR_RW) == 0);\n    }\n\n    zstr_free (&basedirpath);\n    zstr_free (&dirpath);\n    zstr_free (&relsubdir);\n\n    // Other subtests\n    int major, minor, patch;\n    zsys_version (&major, &minor, &patch);\n    assert (major == CZMQ_VERSION_MAJOR);\n    assert (minor == CZMQ_VERSION_MINOR);\n    assert (patch == CZMQ_VERSION_PATCH);\n\n    string = zsys_sprintf ("%s %02x", "Hello", 16);\n    assert (streq (string, "Hello 10"));\n    freen (string);\n\n    char *str64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890,.";\n    int num10 = 1234567890;\n    string = zsys_sprintf ("%s%s%s%s%d", str64, str64, str64, str64, num10);\n    assert (strlen (string) == (4 * 64 + 10));\n    freen (string);\n\n    //  Test logging system\n    zsys_set_logident ("czmq_selftest");\n    zsys_set_logsender ("inproc://logging");\n    void *logger = zsys_socket (ZMQ_SUB, NULL, 0);\n    assert (logger);\n    rc = zmq_connect (logger, "inproc://logging");\n    assert (rc == 0);\n    rc = zmq_setsockopt (logger, ZMQ_SUBSCRIBE, "", 0);\n    assert (rc == 0);\n\n    if (verbose) {\n        zsys_error ("This is an %s message", "error");\n        zsys_warning ("This is a %s message", "warning");\n        zsys_notice ("This is a %s message", "notice");\n        zsys_info ("This is a %s message", "info");\n        zsys_debug ("This is a %s message", "debug");\n        zsys_set_logident ("hello, world");\n        zsys_info ("This is a %s message", "info");\n        zsys_debug ("This is a %s message", "debug");\n\n        //  Check that logsender functionality is working\n        char *received = zstr_recv (logger);\n        assert (received);\n        zstr_free (&received);\n    }\n    zsys_close (logger, NULL, 0);\n\n    {\n        // zhash based printf\n        zhash_t *args = zhash_new ();\n        zhash_insert (args, "key", "value");\n        zhash_insert (args, "ham", "spam");\n\n        char *str = zsys_zprintf ("plain string", args);\n        assert (streq (str, "plain string"));\n        zstr_free (&str);\n\n        str = zsys_zprintf ("%%a%%", args);\n        assert (streq (str, "%a%"));\n        zstr_free (&str);\n\n        str = zsys_zprintf ("VALUE=%(key)s123", args);\n        assert (streq (str, "VALUE=value123"));\n        zstr_free (&str);\n\n        str = zsys_zprintf ("VALUE=%(key)s123, %(ham)s, %(ham)s, %%(nospam)s!!!", args);\n        assert (streq (str, "VALUE=value123, spam, spam, %(nospam)s!!!"));\n        zstr_free (&str);\n\n        str = zsys_zprintf ("VALUE=%(nokey)s123, %(ham)s, %(ham)s, %%(nospam)s!!!", args);\n        assert (!str);\n\n        str = zsys_zprintf_error ("VALUE=%(nokey)s123, %(ham)s, %(ham)s, %%(nospam)s!!!", args);\n        assert (streq (str, "Key \'nokey\' not found in hash"));\n        zstr_free (&str);\n\n        str = zsys_zprintf ("VALUE=%(key)s/%%S", args);\n        assert (streq (str, "VALUE=value/%S"));\n        zstr_free (&str);\n\n        zhash_destroy (&args);\n\n        //ZPL based printf\n        zconfig_t *root = zconfig_new ("root", NULL);\n        zconfig_put (root, "zsp", "");\n        zconfig_put (root, "zsp/return_code", "0");\n\n        str = zsys_zplprintf ("return_code=%(zsp/return_code)s", root);\n        assert (streq (str, "return_code=0"));\n        zstr_free (&str);\n\n        zconfig_destroy (&root);\n    }\n\n    //  @end\n\n    zsys_set_auto_use_fd (1);\n    assert (zsys_auto_use_fd () == 1);\n\n    assert (zsys_max_msgsz () == INT_MAX);\n    zsys_set_max_msgsz (2000);\n    assert (zsys_max_msgsz () == 2000);\n    zsys_set_max_msgsz (-1);\n    assert (zsys_max_msgsz () == 2000);\n\n    // cleanup log_sender\n    zsys_set_logsender(NULL);\n\n#if defined (__WINDOWS__)\n    zsys_shutdown();\n#endif\n\n    printf ("OK\\n");\n}\n', 'message': 'Cosmetic fixes\n', 'author_name': 'Pieter Hintjens', 'lang': 'c'}