1/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */ 2 3#include "src/config.h" 4#include <stdio.h> 5#include <stdlib.h> 6#include <stdbool.h> 7#include <string.h> 8#include <unistd.h> 9#include <fcntl.h> 10#include <platform/cbassert.h> 11#ifndef WIN32 12#include <poll.h> 13#include <limits.h> 14#endif 15#include "memcached.h" 16#include "cproxy.h" 17#include "mcs.h" 18#include "log.h" 19 20/* TODO: This timeout is inherited from zstored, but use it where? */ 21 22#define DOWNSTREAM_DEFAULT_LINGER 1000 23#ifndef INFTIM 24#define INFTIM -1 25#endif 26 27#ifdef WIN32 28static int is_blocking(DWORD dw) { 29 return (dw == WSAEWOULDBLOCK); 30} 31 32static int is_in_progress(DWORD dw) { 33 return (dw == WSAEINPROGRESS); 34} 35#else 36static int is_blocking(int dw) { 37 return (dw == EAGAIN || dw == EWOULDBLOCK); 38} 39 40static int is_in_progress(int dw) { 41 return (dw == EINPROGRESS); 42} 43#endif 44 45 46/* The lvb stands for libvbucket. */ 47 48mcs_st *lvb_create(mcs_st *ptr, const char *config, 49 const char *default_usr, 50 const char *default_pwd, 51 const char *opts); 52void lvb_free_data(mcs_st *ptr); 53bool lvb_stable_update(mcs_st *curr_version, mcs_st *next_version); 54uint32_t lvb_key_hash(mcs_st *ptr, const char *key, size_t key_length, 55 int *vbucket); 56void lvb_server_invalid_vbucket(mcs_st *ptr, int server_index, 57 int vbucket); 58 59/* The lmc stands for libmemcached. */ 60 61mcs_st *lmc_create(mcs_st *ptr, const char *config, 62 const char *default_usr, 63 const char *default_pwd, 64 const char *opts); 65void lmc_free_data(mcs_st *ptr); 66uint32_t lmc_key_hash(mcs_st *ptr, const char *key, size_t key_length, 67 int *vbucket); 68 69/* ---------------------------------------------------------------------- */ 70 71mcs_st *mcs_create(mcs_st *ptr, const char *config, 72 const char *default_usr, 73 const char *default_pwd, 74 const char *opts) { 75 if (config[0] == '{') { 76 if (settings.verbose > 2) { 77 moxi_log_write("mcs_create using libvbucket\n"); 78 } 79 return lvb_create(ptr, config, default_usr, default_pwd, opts); 80 } 81 if (config[0] != '{') { 82 if (settings.verbose > 2) { 83 moxi_log_write("mcs_create using libmemcached\n"); 84 } 85 return lmc_create(ptr, config, default_usr, default_pwd, opts); 86 } 87 moxi_log_write("ERROR: unconfigured hash library\n"); 88 exit(1); 89 90 return NULL; 91} 92 93void mcs_free(mcs_st *ptr) { 94 if (ptr->kind == MCS_KIND_LIBVBUCKET) { 95 lvb_free_data(ptr); 96 } 97 if (ptr->kind == MCS_KIND_LIBMEMCACHED) { 98 lmc_free_data(ptr); 99 } 100 ptr->kind = MCS_KIND_UNKNOWN; 101 102 if (ptr->servers) { 103 int i; 104 for (i = 0; i < ptr->nservers; i++) { 105 if (ptr->servers[i].usr != NULL) { 106 free(ptr->servers[i].usr); 107 } 108 if (ptr->servers[i].pwd != NULL) { 109 free(ptr->servers[i].pwd); 110 } 111 } 112 free(ptr->servers); 113 } 114 115 memset(ptr, 0, sizeof(*ptr)); 116} 117 118bool mcs_stable_update(mcs_st *curr_version, mcs_st *next_version) { 119 if (curr_version->kind == MCS_KIND_LIBVBUCKET) { 120 return lvb_stable_update(curr_version, next_version); 121 } 122 123 /* TODO: MCS_KIND_LIBMEMCACHED impl for stable update. */ 124 125 return false; 126} 127 128uint32_t mcs_server_count(mcs_st *ptr) { 129 return (uint32_t) ptr->nservers; 130} 131 132mcs_server_st *mcs_server_index(mcs_st *ptr, int i) { 133 return &ptr->servers[i]; 134} 135 136uint32_t mcs_key_hash(mcs_st *ptr, const char *key, size_t key_length, 137 int *vbucket) { 138 if (ptr->kind == MCS_KIND_LIBVBUCKET) { 139 return lvb_key_hash(ptr, key, key_length, vbucket); 140 } 141 if (ptr->kind == MCS_KIND_LIBMEMCACHED) { 142 return lmc_key_hash(ptr, key, key_length, vbucket); 143 } 144 return 0; 145} 146 147void mcs_server_invalid_vbucket(mcs_st *ptr, int server_index, 148 int vbucket) { 149 if (ptr->kind == MCS_KIND_LIBVBUCKET) { 150 lvb_server_invalid_vbucket(ptr, server_index, vbucket); 151 } 152} 153 154/* ---------------------------------------------------------------------- */ 155 156mcs_st *lvb_create(mcs_st *ptr, const char *config, 157 const char *default_usr, 158 const char *default_pwd, 159 const char *opts) { 160 VBUCKET_CONFIG_HANDLE vch; 161 (void) opts; 162 163 cb_assert(ptr); 164 memset(ptr, 0, sizeof(*ptr)); 165 ptr->kind = MCS_KIND_LIBVBUCKET; 166 167 vch = vbucket_config_parse_string(config); 168 if (vch != NULL) { 169 ptr->data = vch; 170 ptr->nservers = vbucket_config_get_num_servers(vch); 171 if (ptr->nservers > 0) { 172 ptr->servers = calloc(sizeof(mcs_server_st), ptr->nservers); 173 if (ptr->servers != NULL) { 174 int i, j; 175 for (i = 0; i < ptr->nservers; i++) { 176 ptr->servers[i].fd = -1; 177 } 178 179 for (j = 0; j < ptr->nservers; j++) { 180 const char *user; 181 const char *password; 182 const char *hostport = vbucket_config_get_server(vch, j); 183 if (hostport != NULL && 184 strlen(hostport) > 0 && 185 strlen(hostport) < sizeof(ptr->servers[j].hostname) - 1) { 186 char *colon; 187 strncpy(ptr->servers[j].hostname, 188 hostport, 189 sizeof(ptr->servers[j].hostname) - 1); 190 colon = strchr(ptr->servers[j].hostname, ':'); 191 if (colon != NULL) { 192 *colon = '\0'; 193 ptr->servers[j].port = atoi(colon + 1); 194 if (ptr->servers[j].port <= 0) { 195 moxi_log_write("mcs_create failed, could not parse port: %s\n", 196 config); 197 break; 198 } 199 } else { 200 moxi_log_write("mcs_create failed, missing port: %s\n", 201 config); 202 break; 203 } 204 } else { 205 moxi_log_write("mcs_create failed, unknown server: %s\n", 206 config); 207 break; 208 } 209 210 user = vbucket_config_get_user(vch); 211 if (user != NULL) { 212 ptr->servers[j].usr = strdup(user); 213 } else if (default_usr != NULL) { 214 ptr->servers[j].usr = strdup(default_usr); 215 } 216 217 password = vbucket_config_get_password(vch); 218 if (password != NULL) { 219 ptr->servers[j].pwd = strdup(password); 220 } else if (default_pwd != NULL) { 221 ptr->servers[j].pwd = strdup(default_pwd); 222 } 223 } 224 225 if (j >= ptr->nservers) { 226 return ptr; 227 } 228 } 229 } 230 } else { 231 moxi_log_write("mcs_create failed, vbucket_config_parse_string: %s\n", 232 config); 233 } 234 235 mcs_free(ptr); 236 237 return NULL; 238} 239 240void lvb_free_data(mcs_st *ptr) { 241 cb_assert(ptr->kind == MCS_KIND_LIBVBUCKET); 242 243 if (ptr->data != NULL) { 244 vbucket_config_destroy((VBUCKET_CONFIG_HANDLE) ptr->data); 245 } 246 247 ptr->data = NULL; 248} 249 250/* Returns true if curr_version could be updated with next_version in 251 * a low-impact stable manner (server-list is the same), allowing the 252 * same connections to be reused. Or returns false if the delta was 253 * too large for an in-place updating of curr_version with information 254 * from next_version. 255 * 256 * The next_version may be destroyed in this call, and the caller 257 * should afterwards only call mcs_free() on the next_version. 258 */ 259bool lvb_stable_update(mcs_st *curr_version, mcs_st *next_version) { 260 bool rv = false; 261 VBUCKET_CONFIG_DIFF *diff; 262 263 cb_assert(curr_version->kind == MCS_KIND_LIBVBUCKET); 264 cb_assert(curr_version->data != NULL); 265 cb_assert(next_version->kind == MCS_KIND_LIBVBUCKET); 266 cb_assert(next_version->data != NULL); 267 268 diff = vbucket_compare((VBUCKET_CONFIG_HANDLE) curr_version->data, 269 (VBUCKET_CONFIG_HANDLE) next_version->data); 270 if (diff != NULL) { 271 if (!diff->sequence_changed) { 272 vbucket_config_destroy((VBUCKET_CONFIG_HANDLE) curr_version->data); 273 curr_version->data = next_version->data; 274 next_version->data = 0; 275 276 rv = true; 277 } 278 279 vbucket_free_diff(diff); 280 } 281 282 return rv; 283} 284 285uint32_t lvb_key_hash(mcs_st *ptr, const char *key, size_t key_length, 286 int *vbucket) { 287 VBUCKET_CONFIG_HANDLE vch; 288 int v; 289 290 cb_assert(ptr->kind == MCS_KIND_LIBVBUCKET); 291 cb_assert(ptr->data != NULL); 292 293 vch = (VBUCKET_CONFIG_HANDLE) ptr->data; 294 295 v = vbucket_get_vbucket_by_key(vch, key, key_length); 296 if (vbucket != NULL) { 297 *vbucket = v; 298 } 299 300 return (uint32_t) vbucket_get_master(vch, v); 301} 302 303void lvb_server_invalid_vbucket(mcs_st *ptr, int server_index, 304 int vbucket) { 305 VBUCKET_CONFIG_HANDLE vch; 306 307 cb_assert(ptr->kind == MCS_KIND_LIBVBUCKET); 308 cb_assert(ptr->data != NULL); 309 310 vch = (VBUCKET_CONFIG_HANDLE) ptr->data; 311 312 vbucket_found_incorrect_master(vch, vbucket, server_index); 313} 314 315 316/* ---------------------------------------------------------------------- */ 317 318mcs_st *lmc_create(mcs_st *ptr, const char *config, 319 const char *default_usr, 320 const char *default_pwd, 321 const char *opts) { 322 memcached_st *mst; 323 324 cb_assert(ptr); 325 memset(ptr, 0, sizeof(*ptr)); 326 ptr->kind = MCS_KIND_LIBMEMCACHED; 327 328 mst = memcached_create(NULL); 329 if (mst != NULL) { 330 memcached_server_st *mservers; 331 memcached_behavior_t b = MEMCACHED_BEHAVIOR_KETAMA_WEIGHTED; 332 uint64_t v = 1; 333 334 if (opts != NULL) { 335 if (strstr(opts, "distribution:ketama-weighted") != NULL) { 336 b = MEMCACHED_BEHAVIOR_KETAMA_WEIGHTED; 337 v = 1; 338 } else if (strstr(opts, "distribution:ketama") != NULL) { 339 b = MEMCACHED_BEHAVIOR_KETAMA; 340 v = 1; 341 } else if (strstr(opts, "distribution:modula") != NULL) { 342 b = MEMCACHED_BEHAVIOR_KETAMA; 343 v = 0; 344 } 345 } 346 347 memcached_behavior_set(mst, b, v); 348 memcached_behavior_set(mst, MEMCACHED_BEHAVIOR_NO_BLOCK, 1); 349 memcached_behavior_set(mst, MEMCACHED_BEHAVIOR_TCP_NODELAY, 1); 350 351 mservers = memcached_servers_parse(config); 352 if (mservers != NULL) { 353 memcached_server_push(mst, mservers); 354 355 ptr->data = mst; 356 ptr->nservers = (int) memcached_server_list_count(mservers); 357 if (ptr->nservers > 0) { 358 ptr->servers = calloc(sizeof(mcs_server_st), ptr->nservers); 359 if (ptr->servers != NULL) { 360 int i; 361 int j; 362 for (i = 0; i < ptr->nservers; i++) { 363 ptr->servers[i].fd = -1; 364 } 365 366 for (j = 0; j < ptr->nservers; j++) { 367 strncpy(ptr->servers[j].hostname, 368 memcached_server_name(mservers + j), 369 sizeof(ptr->servers[j].hostname) - 1); 370 ptr->servers[j].port = 371 (int) memcached_server_port(mservers + j); 372 if (ptr->servers[j].port <= 0) { 373 moxi_log_write("lmc_create failed, could not parse port: %s\n", 374 config); 375 break; 376 } 377 378 if (default_usr != NULL) { 379 ptr->servers[j].usr = strdup(default_usr); 380 } 381 382 if (default_pwd != NULL) { 383 ptr->servers[j].pwd = strdup(default_pwd); 384 } 385 } 386 387 if (j >= ptr->nservers) { 388 memcached_server_list_free(mservers); 389 390 return ptr; 391 } 392 } 393 } 394 395 memcached_server_list_free(mservers); 396 } 397 } 398 399 mcs_free(ptr); 400 401 return NULL; 402} 403 404void lmc_free_data(mcs_st *ptr) { 405 cb_assert(ptr->kind == MCS_KIND_LIBMEMCACHED); 406 407 if (ptr->data != NULL) { 408 memcached_free((memcached_st *) ptr->data); 409 } 410 411 ptr->data = NULL; 412} 413 414uint32_t lmc_key_hash(mcs_st *ptr, const char *key, size_t key_length, int *vbucket) { 415 cb_assert(ptr->kind == MCS_KIND_LIBMEMCACHED); 416 cb_assert(ptr->data != NULL); 417 418 if (vbucket != NULL) { 419 *vbucket = -1; 420 } 421 422 return memcached_generate_hash((memcached_st *) ptr->data, key, key_length); 423} 424 425/* ---------------------------------------------------------------------- */ 426 427void mcs_server_st_quit(mcs_server_st *ptr, uint8_t io_death) { 428 (void) io_death; 429 430 /* TODO: Should send QUIT cmd. */ 431 432 if (ptr->fd != INVALID_SOCKET) { 433 closesocket(ptr->fd); 434 } 435 ptr->fd = INVALID_SOCKET; 436} 437 438mcs_return mcs_server_st_connect(mcs_server_st *ptr, int *errno_out, bool blocking) { 439 if (ptr->fd != INVALID_SOCKET) { 440 if (errno_out != NULL) { 441 *errno_out = 0; 442 } 443 444 return MCS_SUCCESS; 445 } 446 447 if (errno_out != NULL) { 448 *errno_out = -1; 449 } 450 451 ptr->fd = mcs_connect(ptr->hostname, ptr->port, errno_out, blocking); 452 if (ptr->fd != INVALID_SOCKET) { 453 return MCS_SUCCESS; 454 } 455 456 return MCS_FAILURE; 457} 458 459SOCKET mcs_connect(const char *hostname, int portnum, 460 int *errno_out, bool blocking) { 461 SOCKET ret = INVALID_SOCKET; 462 struct addrinfo *ai = NULL; 463 struct addrinfo *next = NULL; 464 struct addrinfo hints; 465 char port[50]; 466 int error; 467 468 if (errno_out != NULL) { 469 *errno_out = -1; 470 } 471 472 memset(&hints, 0, sizeof(struct addrinfo)); 473 hints.ai_flags = AI_PASSIVE; 474 hints.ai_socktype = SOCK_STREAM; 475 hints.ai_family = AF_UNSPEC; 476 477 snprintf(port, sizeof(port), "%d", portnum); 478 479 error = getaddrinfo(hostname, port, &hints, &ai); 480 if (error != 0) { 481#if 0 482 if (error != EAI_SYSTEM) { 483 /* settings.extensions.logger->log(EXTENSION_LOG_WARNING, NULL, */ 484 /* "getaddrinfo(): %s\n", gai_strerror(error)); */ 485 } else { 486 /* settings.extensions.logger->log(EXTENSION_LOG_WARNING, NULL, */ 487 /* "getaddrinfo(): %s\n", strerror(error)); */ 488 } 489#endif 490 return INVALID_SOCKET; 491 } 492 493 for (next = ai; next; next = next->ai_next) { 494 SOCKET sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); 495 if (sock == INVALID_SOCKET) { 496 /* settings.extensions.logger->log(EXTENSION_LOG_WARNING, NULL, */ 497 /* "Failed to create socket: %s\n", */ 498 /* strerror(errno)); */ 499 continue; 500 } 501 502 /* If the caller wants non-blocking, set the sock options */ 503 /* now so even the connect() becomes non-blocking. */ 504 505 if (!blocking && (mcs_set_sock_opt(sock) != MCS_SUCCESS)) { 506 closesocket(sock); 507 continue; 508 } 509 510 if (connect(sock, ai->ai_addr, (socklen_t)ai->ai_addrlen) == SOCKET_ERROR) { 511#ifdef WIN32 512 DWORD errno_last = WSAGetLastError(); 513#else 514 int errno_last = errno; 515#endif 516 if (errno_out != NULL) { 517 *errno_out = errno_last; 518 } 519 520 if (!blocking && (is_in_progress(errno_last) || 521 is_blocking(errno_last))) { 522 ret = sock; 523 break; 524 } 525 526 /* settings.extensions.logger->log(EXTENSION_LOG_WARNING, NULL, */ 527 /* "Failed to connect socket: %s\n", */ 528 /* strerror(errno)); */ 529 closesocket(sock); 530 continue; 531 } 532 533 if (mcs_set_sock_opt(sock) == MCS_SUCCESS) { 534 ret = sock; 535 break; 536 } 537 538 closesocket(sock); 539 } 540 541 freeaddrinfo(ai); 542 543 return ret; 544} 545 546mcs_return mcs_set_sock_opt(SOCKET sock) { 547 /* jsh: todo 548 TODO: from zstored set_socket_options()... 549 550 if (fd type == MEMCACHED_CONNECTION_UDP) 551 return true; 552 553#ifdef HAVE_SNDTIMEO 554 if (ptr->root->snd_timeout) { 555 int error; 556 struct timeval waittime; 557 558 waittime.tv_sec = 0; 559 waittime.tv_usec = ptr->root->snd_timeout; 560 561 error = setsockopt(ptr->fd, SOL_SOCKET, SO_SNDTIMEO, 562 &waittime, (socklen_t)sizeof(struct timeval)); 563 WATCHPOINT_ASSERT(error == 0); 564 } 565#endif 566 567#ifdef HAVE_RCVTIMEO 568 if (ptr->root->rcv_timeout) { 569 int error; 570 struct timeval waittime; 571 572 waittime.tv_sec = 0; 573 waittime.tv_usec = ptr->root->rcv_timeout; 574 575 error= setsockopt(ptr->fd, SOL_SOCKET, SO_RCVTIMEO, 576 &waittime, (socklen_t)sizeof(struct timeval)); 577 WATCHPOINT_ASSERT(error == 0); 578 } 579#endif 580 581 { 582 int error; 583 struct linger linger; 584 585 linger.l_onoff = 1; 586 linger.l_linger = DOWNSTREAM_DEFAULT_LINGER; 587 error = setsockopt(fd, SOL_SOCKET, SO_LINGER, 588 &linger, (socklen_t)sizeof(struct linger)); 589 } 590 591 if (ptr->root->send_size) { 592 int error; 593 594 error= setsockopt(ptr->fd, SOL_SOCKET, SO_SNDBUF, 595 &ptr->root->send_size, (socklen_t)sizeof(int)); 596 WATCHPOINT_ASSERT(error == 0); 597 } 598 599 if (ptr->root->recv_size) { 600 int error; 601 602 error= setsockopt(ptr->fd, SOL_SOCKET, SO_RCVBUF, 603 &ptr->root->recv_size, (socklen_t)sizeof(int)); 604 WATCHPOINT_ASSERT(error == 0); 605 } 606 */ 607 if (evutil_make_socket_nonblocking(sock) == -1) { 608 return MCS_FAILURE; 609 } 610 611 int flags = 1; 612 613 setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, 614 (void*)&flags, (socklen_t)sizeof(flags)); 615 616 return MCS_SUCCESS; 617} 618 619ssize_t mcs_io_write(SOCKET fd, const void *buffer, size_t length) { 620 cb_assert(fd != -1); 621 622 return send(fd, buffer, (int)length, 0); 623} 624 625#ifdef WIN32 626mcs_return mcs_io_read(SOCKET fd, void *dta, size_t size, struct timeval *timeout_in) { 627 struct timeval my_timeout; /* Linux select() modifies its timeout param. */ 628 struct timeval *timeout = NULL; 629 630 if (timeout_in != NULL && 631 (timeout_in->tv_sec != 0 || 632 timeout_in->tv_usec != 0)) { 633 my_timeout = *timeout_in; 634 timeout = &my_timeout; 635 } 636 637 char *data = dta; 638 size_t done = 0; 639 640 while (done < size) { 641 fd_set readfds[FD_SETSIZE]; 642 FD_ZERO(readfds); 643 FD_SET(fd, readfds); 644 645 fd_set errfds[FD_SETSIZE]; 646 FD_ZERO(errfds); 647 FD_SET(fd, errfds); 648 649 int s = select(FD_SETSIZE, readfds, NULL, errfds, timeout); 650 if (s == 0) { 651 return MCS_TIMEOUT; 652 } 653 654 if (s != 1 || FD_ISSET(fd, errfds) || !FD_ISSET(fd, readfds)) { 655 return MCS_FAILURE; 656 } 657 658 ssize_t n = recv(fd, data + done, 1, 0); 659 if (n == -1 || n == 0) { 660 return MCS_FAILURE; 661 } 662 663 done += (size_t) n; 664 } 665 666 return MCS_SUCCESS; 667} 668#else 669 670static uint64_t __get_time_ms(const struct timeval *tv) { 671 struct timeval now; 672 673 if (tv == NULL) { 674 if (gettimeofday(&now, NULL) != 0) { 675 return 0; 676 } 677 tv = &now; 678 } 679 return (uint64_t)tv->tv_sec * 1000 + (uint64_t)tv->tv_usec / 1000; 680} 681 682mcs_return mcs_io_read(SOCKET fd, void *dta, size_t size, struct timeval *timeout_in) { 683 uint64_t start_ms = 0; 684 uint64_t timeout_ms = 0; 685 uint64_t now_ms = 0; 686 char *data; 687 size_t done; 688 struct pollfd pfd[1]; 689 690 if (timeout_in != NULL) { 691 start_ms = __get_time_ms(NULL); 692 timeout_ms = __get_time_ms(timeout_in); 693 now_ms = start_ms; 694 } 695 696 data = dta; 697 done = 0; 698 699 while (done < size) { 700 int timeout = INFTIM; 701 int s; 702 ssize_t n; 703 704 pfd[0].fd = fd; 705 pfd[0].events = POLLIN; 706 pfd[0].revents = 0; 707 708 if (timeout_in != NULL) { 709 if (timeout_ms == 0) { 710 /* ensure we poll at least once */ 711 timeout = 0; 712 } else { 713 uint64_t taken_ms = now_ms - start_ms; 714 if (taken_ms >= timeout_ms) { 715 /* just check (boundary case) */ 716 timeout = 0; 717 } else { 718 uint64_t left_ms = timeout_ms - taken_ms; 719 timeout = (left_ms > INT_MAX) ? INT_MAX : left_ms; 720 } 721 } 722 } 723 s = poll(pfd, 1, timeout); 724 if (s == 0) { 725 return MCS_TIMEOUT; 726 } 727 728 if (s != 1 || (pfd[0].revents & (POLLERR|POLLHUP|POLLNVAL)) || !(pfd[0].revents & POLLIN)) { 729 return MCS_FAILURE; 730 } 731 732 n = read(fd, data + done, 1); 733 if (n == -1 || n == 0) { 734 return MCS_FAILURE; 735 } 736 737 done += (size_t) n; 738 now_ms = __get_time_ms(NULL); 739 } 740 741 return MCS_SUCCESS; 742} 743#endif 744 745void mcs_io_reset(SOCKET fd) { 746 (void) fd; 747 748 /* TODO: memcached_io_reset(ptr); */ 749} 750 751const char *mcs_server_st_hostname(mcs_server_st *ptr) { 752 return ptr->hostname; 753} 754 755int mcs_server_st_port(mcs_server_st *ptr) { 756 return ptr->port; 757} 758 759SOCKET mcs_server_st_fd(mcs_server_st *ptr) { 760 return ptr->fd; 761} 762 763const char *mcs_server_st_usr(mcs_server_st *ptr) { 764 return ptr->usr; 765} 766 767const char *mcs_server_st_pwd(mcs_server_st *ptr) { 768 return ptr->pwd; 769} 770 771char *mcs_server_st_ident(mcs_server_st *msst, bool is_ascii) { 772 char *buf; 773 cb_assert(msst != NULL); 774 775 buf = is_ascii ? msst->ident_a : msst->ident_b; 776 if (buf[0] == '\0') { 777 const char *usr = mcs_server_st_usr(msst); 778 const char *pwd = mcs_server_st_pwd(msst); 779 780 snprintf(buf, MCS_IDENT_SIZE, 781 "%s:%d:%s:%s:%d", 782 mcs_server_st_hostname(msst), 783 mcs_server_st_port(msst), 784 usr, pwd, 785 is_ascii); 786 } 787 788 return buf; 789} 790