File: wsrep_utils.cc

package info (click to toggle)
mariadb 1%3A11.8.3-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 772,520 kB
  • sloc: ansic: 2,414,714; cpp: 1,791,394; asm: 381,336; perl: 62,905; sh: 49,647; pascal: 40,897; java: 39,363; python: 20,791; yacc: 20,432; sql: 17,907; xml: 12,344; ruby: 8,544; cs: 6,542; makefile: 6,145; ada: 1,879; lex: 1,193; javascript: 996; objc: 80; tcl: 73; awk: 46; php: 22
file content (729 lines) | stat: -rw-r--r-- 19,018 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
/* Copyright 2010-2015 Codership Oy <http://www.codership.com>

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
 */

//! @file some utility functions and classes not directly related to replication

#ifndef _GNU_SOURCE
#define _GNU_SOURCE // POSIX_SPAWN_USEVFORK flag
#endif

#include "mariadb.h"
#include "my_global.h"
#include "wsrep_api.h"
#include "wsrep_utils.h"
#include "wsrep_mysqld.h"
#include "wsrep_thd.h"

#include <sql_class.h>

#include <spawn.h>    // posix_spawn()
#include <unistd.h>   // pipe()
#include <errno.h>    // errno
#include <string.h>   // strerror()
#include <sys/wait.h> // waitpid()
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>    // getaddrinfo()

#ifdef HAVE_GETIFADDRS
#include <net/if.h>
#include <ifaddrs.h>
#endif /* HAVE_GETIFADDRS */

#define MY_MALLOC(arg) my_malloc(key_memory_WSREP, arg, MYF(0))
#define MY_REALLOC(p, arg) my_realloc(key_memory_WSREP, p, arg, MYF(MY_ALLOW_ZERO_PTR))
#define MY_STRDUP(arg) my_strdup(key_memory_WSREP, arg, MYF(0))
#define MY_FREE(arg) my_free(arg)

extern char** environ; // environment variables

static wsp::string wsrep_PATH;

void
wsrep_prepend_PATH (const char* path)
{
    int count= 0;

    while (environ[count])
    {
        if (strncmp (environ[count], "PATH=", 5))
        {
            count++;
            continue;
        }

        char* const old_path (environ[count]);

        if (strstr (old_path, path)) return; // path already there

        size_t const new_path_len(strlen(old_path) + strlen(":") +
                                  strlen(path) + 1);

        char* const new_path (static_cast<char*>(malloc(new_path_len)));

        if (new_path)
        {
            snprintf (new_path, new_path_len, "PATH=%s:%s", path,
                      old_path + strlen("PATH="));

            wsrep_PATH.set (new_path);
            environ[count]= new_path;
        }
        else
        {
            WSREP_ERROR ("Failed to allocate 'PATH' environment variable "
                         "buffer of size %zu.", new_path_len);
        }

        return;
    }

    WSREP_ERROR ("Failed to find 'PATH' environment variable. "
                 "State snapshot transfer may not be working.");
}

namespace wsp
{

bool
env::ctor_common(char** e)
{
    env_= static_cast<char**>(MY_MALLOC((len_ + 1) * sizeof(char*)));

    if (env_)
    {
        for (size_t i(0); i < len_; ++i)
        {
            assert(e[i]); // caller should make sure about len_
            env_[i]= MY_STRDUP(e[i]);
            if (!env_[i])
            {
                errno_= errno;
                WSREP_ERROR("Failed to allocate env. var: %s", e[i]);
                return true;
            }
        }

        env_[len_]= NULL;
        return false;
    }
    else
    {
        errno_= errno;
        WSREP_ERROR("Failed to allocate env. var vector of length: %zu", len_);
        return true;
    }
}

void
env::dtor()
{
    if (env_)
    {
        /* don't need to go beyond the first NULL */
        for (size_t i(0); env_[i] != NULL; ++i) { MY_FREE(env_[i]); }
        MY_FREE(env_);
        env_= NULL;
    }
    len_= 0;
}

env::env(char** e)
    : len_(0), env_(NULL), errno_(0)
{
    if (!e) { e= environ; }
    /* count the size of the vector */
    while (e[len_]) { ++len_; }

    if (ctor_common(e)) dtor();
}

env::env(const env& e)
    : len_(e.len_), env_(0), errno_(0)
{
    if (ctor_common(e.env_)) dtor();
}

env::~env() { dtor(); }

int
env::append(const char* val)
{
    char** tmp= static_cast<char**>(MY_REALLOC(env_, (len_ + 2)*sizeof(char*)));

    if (tmp)
    {
        env_= tmp;
        env_[len_]= MY_STRDUP(val);

        if (env_[len_])
        {
            ++len_;
            env_[len_]= NULL;
        }
        else errno_= errno;
    }
    else errno_= errno;

    return errno_;
}

#define READ_END  0
#define WRITE_END 1
#define STDIN_FD  0
#define STDOUT_FD 1

#ifndef POSIX_SPAWN_USEVFORK
# define POSIX_SPAWN_USEVFORK 0
#endif

static int
add_file_actions(posix_spawn_file_actions_t *fact,
                 int close_fd, int dup_fd, int unused_fd)
{
    // close child's stdout|stdin fd
    int err= posix_spawn_file_actions_addclose(fact, close_fd);
    if (err)
    {
        WSREP_ERROR ("posix_spawn_file_actions_addclose() failed: %d (%s)",
                   err, strerror(err));
        return err;
    }

    // substitute our pipe descriptor in place of the closed one
    err= posix_spawn_file_actions_adddup2(fact, dup_fd, close_fd);
    if (err)
    {
        WSREP_ERROR ("posix_spawn_file_actions_addup2() failed: %d (%s)",
                     err, strerror(err));
        return err;
    }

    // close unused end of the pipe
    err= posix_spawn_file_actions_addclose(fact, unused_fd);
    if (err)
    {
        WSREP_ERROR ("posix_spawn_file_actions_addclose(2) failed: %d (%s)",
                     err, strerror(err));
        return err;
    }

    return 0;
}

void
process::setup_parent_pipe_end(io_direction      direction,
                               int               pipe_fds[],
                               int const         pipe_end,
                               const char* const mode)
{
    io_[direction] = fdopen(pipe_fds[pipe_end], mode);

    if (io_[direction])
    {
        pipe_fds[pipe_end]= -1; // skip close on cleanup
    }
    else
    {
        err_= errno;
        WSREP_ERROR("fdopen() failed on '%s' pipe: %d (%s)",
                    mode, err_, strerror(err_));
    }
}

void
process::close_io(io_direction const direction, bool const warn)
{
    if (io_[direction])
    {
        if (warn)
        {
            WSREP_WARN("Closing pipe to child process: %s, PID(%ld) "
                       "which might still be running.", str_, (long)pid_);
        }

        if (fclose(io_[direction]) == -1)
        {
            err_= errno;
            WSREP_ERROR("fclose(%d) failed: %d (%s)",
                        direction, err_, strerror(err_));
        }

        io_[direction]= NULL;
    }
}

process::process (const char* cmd, const char* type, char** env)
    :
    str_(cmd ? MY_STRDUP(cmd) : MY_STRDUP("")),
    io_{ NULL, NULL },
    err_(EINVAL),
    pid_(0)
{
    if (0 == str_)
    {
        WSREP_ERROR ("Can't allocate command line of size: %zu", strlen(cmd));
        err_= ENOMEM;
        return;
    }

    if (0 == strlen(str_))
    {
        WSREP_ERROR ("Can't start a process: null or empty command line.");
        return;
    }

    if (NULL == type ||
        (strncmp(type, "w", 1) && strncmp(type, "r", 1) && strncmp(type, "rw", 2)))
    {
        WSREP_ERROR ("type argument should be either \"r\", \"w\" or \"rw\".");
        return;
    }

    if (NULL == env) { env= environ; } // default to global environment

    bool const read_from_child= strchr(type, 'r');
    bool const write_to_child= strchr(type, 'w');

    int read_pipe[2]=  { -1, -1 };
    int write_pipe[2]= { -1, -1 };

    char* const pargv[4]= { MY_STRDUP("sh"), MY_STRDUP("-c"), MY_STRDUP(str_), NULL };
    if (!(pargv[0] && pargv[1] && pargv[2]))
    {
        err_= ENOMEM;
        WSREP_ERROR ("Failed to allocate pargv[] array.");
        goto cleanup_pargv;
    }

    if (read_from_child && ::pipe(read_pipe))
    {
        err_= errno;
        WSREP_ERROR ("pipe(read_pipe) failed: %d (%s)", err_, strerror(err_));
        goto cleanup_pargv;
    }

    if (write_to_child && ::pipe(write_pipe))
    {
        err_= errno;
        WSREP_ERROR ("pipe(write_pipe) failed: %d (%s)", err_, strerror(err_));
        goto cleanup_pipes;
    }

    posix_spawnattr_t attr;
    err_= posix_spawnattr_init (&attr);
    if (err_)
    {
        WSREP_ERROR ("posix_spawnattr_init() failed: %d (%s)",
                     err_, strerror(err_));
        goto cleanup_pipes;
    }

    /* make sure that no signlas are masked in child process */
    sigset_t sigmask_empty; sigemptyset(&sigmask_empty);
    err_= posix_spawnattr_setsigmask(&attr, &sigmask_empty);
    if (err_)
    {
        WSREP_ERROR ("posix_spawnattr_setsigmask() failed: %d (%s)",
                     err_, strerror(err_));
        goto cleanup_attr;
    }

    /* make sure the following signals are not ignored in child process */
    sigset_t default_signals; sigemptyset(&default_signals);
    sigaddset(&default_signals, SIGHUP);
    sigaddset(&default_signals, SIGINT);
    sigaddset(&default_signals, SIGQUIT);
    sigaddset(&default_signals, SIGPIPE);
    sigaddset(&default_signals, SIGTERM);
    sigaddset(&default_signals, SIGCHLD);
    err_= posix_spawnattr_setsigdefault(&attr, &default_signals);
    if (err_)
    {
        WSREP_ERROR ("posix_spawnattr_setsigdefault() failed: %d (%s)",
                     err_, strerror(err_));
        goto cleanup_attr;
    }

    err_= posix_spawnattr_setflags (&attr, POSIX_SPAWN_SETSIGDEF  |
                                           POSIX_SPAWN_SETSIGMASK |
                                           POSIX_SPAWN_USEVFORK);
    if (err_)
    {
        WSREP_ERROR ("posix_spawnattr_setflags() failed: %d (%s)",
                     err_, strerror(err_));
        goto cleanup_attr;
    }

    posix_spawn_file_actions_t fact;
    err_= posix_spawn_file_actions_init (&fact);
    if (err_)
    {
        WSREP_ERROR ("posix_spawn_file_actions_init() failed: %d (%s)",
                     err_, strerror(err_));
        goto cleanup_attr;
    }

    /* Add file actions for the child (fd substitution, close unused fds) */
    if (read_from_child)
    {
        err_= add_file_actions(&fact, STDOUT_FD, read_pipe[WRITE_END],
                               read_pipe[READ_END]);
        if (err_) goto cleanup_fact;
    }

    if (write_to_child)
    {
        err_= add_file_actions(&fact, STDIN_FD, write_pipe[READ_END],
                               write_pipe[WRITE_END]);
        if (err_) goto cleanup_fact;
    }

    err_= posix_spawnp (&pid_, pargv[0], &fact, &attr, pargv, env);
    if (err_)
    {
        WSREP_ERROR ("posix_spawnp(%s) failed: %d (%s)",
                     pargv[2], err_, strerror(err_));
        pid_= 0; // just to make sure it was not messed up in the call
        goto cleanup_fact;
    }

    if (read_from_child)
    {
        setup_parent_pipe_end(READ, read_pipe, READ_END, "r");
        assert(from());
    }

    if (write_to_child)
    {
        setup_parent_pipe_end(WRITE, write_pipe, WRITE_END, "w");
        assert(to());
    }

cleanup_fact:
    int err; // to preserve err_ code
    err= posix_spawn_file_actions_destroy (&fact);
    if (err)
    {
        WSREP_ERROR ("posix_spawn_file_actions_destroy() failed: %d (%s)\n",
                     err, strerror(err));
    }

cleanup_attr:
    err= posix_spawnattr_destroy (&attr);
    if (err)
    {
        WSREP_ERROR ("posix_spawnattr_destroy() failed: %d (%s)",
                     err, strerror(err));
    }

cleanup_pipes:
    if (read_pipe[0] >= 0) close (read_pipe[0]);
    if (read_pipe[1] >= 0) close (read_pipe[1]);
    if (write_pipe[0] >= 0) close (write_pipe[0]);
    if (write_pipe[1] >= 0) close (write_pipe[1]);

cleanup_pargv:
    MY_FREE(pargv[0]);
    MY_FREE(pargv[1]);
    MY_FREE(pargv[2]);
}

process::~process ()
{
    close_io(READ, true);
    close_io(WRITE, true);

    if (str_) MY_FREE(const_cast<char*>(str_));
}

int
process::wait ()
{
  if (pid_)
  {
      int status;
      if (-1 == waitpid(pid_, &status, 0))
      {
          err_= errno; assert (err_);
          WSREP_ERROR("Waiting for process failed: %s, PID(%ld): %d (%s)",
                      str_, (long)pid_, err_, strerror (err_));
      }
      else
      {                // command completed, check exit status
          if (WIFEXITED (status)) {
              err_= WEXITSTATUS (status);
          }
          else {       // command didn't complete with exit()
              WSREP_ERROR("Process was aborted.");
              err_= errno ? errno : ECHILD;
          }

          if (err_) {
              switch (err_) /* Translate error codes to more meaningful */
              {
              case 126: err_= EACCES; break; /* Permission denied */
              case 127: err_= ENOENT; break; /* No such file or directory */
              case 143: err_= EINTR;  break; /* Subprocess killed */
              }
              WSREP_ERROR("Process completed with error: %s: %d (%s)",
                          str_, err_, strerror(err_));
          }

          pid_= 0;
          close_io(READ,  false);
          close_io(WRITE, false);
       }
  }
  else {
      assert (NULL == io_[READ]);
      assert (NULL == io_[WRITE]);
      WSREP_ERROR("Command did not run: %s", str_);
  }

  return err_;
}

thd::thd (my_bool ini, bool system_thread)
    :
    init(ini),
    ptr(init.err_ ? nullptr : new THD(0))
{
  if (ptr)
  {
    ptr->real_id= pthread_self();
    wsrep_assign_from_threadvars(ptr);
    wsrep_store_threadvars(ptr);

    ptr->variables.tx_isolation= ISO_READ_COMMITTED;
    ptr->variables.sql_log_bin = 0;
    ptr->variables.option_bits &= ~OPTION_BIN_LOG; // disable binlog
    ptr->variables.option_bits |=  OPTION_LOG_OFF; // disable general log
    ptr->variables.wsrep_on = false;
    ptr->security_context()->skip_grants();

    if (system_thread)
    {
      ptr->system_thread= SYSTEM_THREAD_GENERIC;
    }
    ptr->security_ctx->master_access= ALL_KNOWN_ACL;
    lex_start(ptr);
  }
}

thd::~thd ()
{
  if (ptr)
  {
    delete ptr;
    set_current_thd(nullptr);
  }
}

mysql::mysql() :
  mysql_(mysql_init(NULL))
{
  int err = 0;
  if (mysql_real_connect_local(mysql_) == NULL) {
      err = mysql_errno(mysql_);
      WSREP_ERROR("mysql::mysql() mysql_real_connect() failed: %d (%s)",
                  err, mysql_error(mysql_));
  }
}

mysql::~mysql()
{
  mysql_close(mysql_);
}

int
mysql::disable_replication()
{
  int err = execute("SET SESSION sql_log_bin = OFF;");
  if (err) {
    WSREP_ERROR("sst_user::user() disabling log_bin failed: %d (%s)",
                err, errstr());
  }
  else {
    err = execute("SET SESSION wsrep_on = OFF;");
    if (err) {
      WSREP_ERROR("sst_user::user() disabling wsrep replication failed: %d (%s)",
                  err, errstr());
    }
  }
  return err;
}

} // namespace wsp

/* Returns INADDR_NONE, INADDR_ANY, INADDR_LOOPBACK or something else */
unsigned int wsrep_check_ip (const char* const addr, bool *is_ipv6)
{
  unsigned int ret= INADDR_NONE;
  struct addrinfo *res, hints;

  memset (&hints, 0, sizeof(hints));
  hints.ai_flags= AI_PASSIVE/*|AI_ADDRCONFIG*/;
  hints.ai_socktype= SOCK_STREAM;
  hints.ai_family= AF_UNSPEC;

  *is_ipv6= false;

  char *end;
  char address[INET6_ADDRSTRLEN];

  end= strcend(addr, ',');
  strmake(address, addr, (uint) (end - addr));

  int gai_ret= getaddrinfo(address, NULL, &hints, &res);
  if (0 == gai_ret)
  {
    if (AF_INET == res->ai_family) /* IPv4 */
    {
      struct sockaddr_in* a= (struct sockaddr_in*)res->ai_addr;
      ret= htonl(a->sin_addr.s_addr);
    }
    else /* IPv6 */
    {
      struct sockaddr_in6* a= (struct sockaddr_in6*)res->ai_addr;
      if (IN6_IS_ADDR_UNSPECIFIED(&a->sin6_addr))
        ret= INADDR_ANY;
      else if (IN6_IS_ADDR_LOOPBACK(&a->sin6_addr))
        ret= INADDR_LOOPBACK;
      else
        ret= 0xdeadbeef;

      *is_ipv6= true;
    }
    freeaddrinfo (res);
  }
  else {
    WSREP_ERROR ("getaddrinfo() failed on '%s': %d (%s)",
                 addr, gai_ret, gai_strerror(gai_ret));
  }

  return ret;
}

extern char* my_bind_addr_str;

size_t wsrep_guess_ip (char* buf, size_t buf_len)
{
  size_t ret= 0;

  // Attempt 1: Try to get the IP from bind-address.
  // Skip if empty or bind-address=*
  if (my_bind_addr_str && my_bind_addr_str[0] != '\0' &&
      strcmp(my_bind_addr_str, "*") != 0)
  {
    bool unused;
    unsigned int const ip_type= wsrep_check_ip(my_bind_addr_str, &unused);

    if (INADDR_NONE == ip_type) {
      WSREP_ERROR("Networking not configured, cannot receive state "
                  "transfer.");
      ret= 0;
      goto done;
    } else if (INADDR_ANY != ip_type) {
      strncpy (buf, my_bind_addr_str, buf_len);
      ret= strlen(buf);
      goto done;
    }
  }

  // Attempt 2: mysqld binds to all interfaces, try IP from wsrep_node_address.
  if (wsrep_node_address && wsrep_node_address[0] != '\0') {
    wsp::Address addr(wsrep_node_address);
    if (!addr.is_valid())
    {
      WSREP_WARN("Could not parse wsrep_node_address : %s",
                 wsrep_node_address);
      ret= 0;
      goto done;
    }

    /* Safety check: Buffer length should be sufficiently large. */
    DBUG_ASSERT(buf_len >= addr.get_address_len());

    memcpy(buf, addr.get_address(), addr.get_address_len());
    ret= addr.get_address_len();
    goto done;
  }

  /*
    Attempt 3: Try to get the IP from the list of available interfaces.

    getifaddrs() is avaiable at least on Linux since glib 2.3, FreeBSD,
    MAC OSX, OpenSolaris, Solaris.

    On platforms which do not support getifaddrs() this function returns
    a failure and user is prompted to do manual configuration.
  */
#if HAVE_GETIFADDRS
  struct ifaddrs *ifaddr, *ifa;
  int family;

  if (getifaddrs(&ifaddr) == 0)
  {
    for (ifa= ifaddr; ifa != NULL; ifa= ifa->ifa_next)
    {
      if (!ifa->ifa_addr)
        continue;

      family= ifa->ifa_addr->sa_family;

      if ((family != AF_INET) && (family != AF_INET6))
        continue;

      // Skip loopback interfaces (like lo:127.0.0.1)
      if (ifa->ifa_flags & IFF_LOOPBACK)
        continue;

      /*
        Get IP address from the socket address. The resulting address may have
        zone ID appended for IPv6 addresses (<address>%<zone-id>).
      */
      if (vio_getnameinfo(ifa->ifa_addr, buf, buf_len, NULL, 0, NI_NUMERICHOST))
        continue;

      freeifaddrs(ifaddr);

      ret= strlen(buf);
      goto done;
    }
    freeifaddrs(ifaddr);
  }
#endif /* HAVE_GETIFADDRS */

done:
  WSREP_DEBUG("wsrep_guess_ip() : %s", (ret > 0) ? buf : "????");
  return ret;
}

/* returns the length of the host part of the address string */
size_t wsrep_host_len(const char* const addr, size_t const addr_len)
{
  // check for IPv6 notation first
  const char* const bracket= ('[' == addr[0] ? strchr(addr, ']') : NULL);

  if (bracket) { // IPv6
    return (bracket - addr + 1);
  }
  else { // host part ends at ':' or end of string
    const char* const colon= strchr(addr, ':');
    return (colon ? colon - addr : addr_len);
  }
}