diff options
Diffstat (limited to 'tools')
-rw-r--r-- | tools/hv/hv_kvp_daemon.c | 337 |
1 files changed, 249 insertions, 88 deletions
diff --git a/tools/hv/hv_kvp_daemon.c b/tools/hv/hv_kvp_daemon.c index d9834b362943..65d54c89394e 100644 --- a/tools/hv/hv_kvp_daemon.c +++ b/tools/hv/hv_kvp_daemon.c | |||
@@ -69,15 +69,16 @@ enum key_index { | |||
69 | }; | 69 | }; |
70 | 70 | ||
71 | static char kvp_send_buffer[4096]; | 71 | static char kvp_send_buffer[4096]; |
72 | static char kvp_recv_buffer[4096]; | 72 | static char kvp_recv_buffer[4096 * 2]; |
73 | static struct sockaddr_nl addr; | 73 | static struct sockaddr_nl addr; |
74 | static int in_hand_shake = 1; | ||
74 | 75 | ||
75 | static char *os_name = ""; | 76 | static char *os_name = ""; |
76 | static char *os_major = ""; | 77 | static char *os_major = ""; |
77 | static char *os_minor = ""; | 78 | static char *os_minor = ""; |
78 | static char *processor_arch; | 79 | static char *processor_arch; |
79 | static char *os_build; | 80 | static char *os_build; |
80 | static char *lic_version; | 81 | static char *lic_version = "Unknown version"; |
81 | static struct utsname uts_buf; | 82 | static struct utsname uts_buf; |
82 | 83 | ||
83 | 84 | ||
@@ -394,7 +395,7 @@ static int kvp_get_value(int pool, __u8 *key, int key_size, __u8 *value, | |||
394 | return 1; | 395 | return 1; |
395 | } | 396 | } |
396 | 397 | ||
397 | static void kvp_pool_enumerate(int pool, int index, __u8 *key, int key_size, | 398 | static int kvp_pool_enumerate(int pool, int index, __u8 *key, int key_size, |
398 | __u8 *value, int value_size) | 399 | __u8 *value, int value_size) |
399 | { | 400 | { |
400 | struct kvp_record *record; | 401 | struct kvp_record *record; |
@@ -406,16 +407,12 @@ static void kvp_pool_enumerate(int pool, int index, __u8 *key, int key_size, | |||
406 | record = kvp_file_info[pool].records; | 407 | record = kvp_file_info[pool].records; |
407 | 408 | ||
408 | if (index >= kvp_file_info[pool].num_records) { | 409 | if (index >= kvp_file_info[pool].num_records) { |
409 | /* | 410 | return 1; |
410 | * This is an invalid index; terminate enumeration; | ||
411 | * - a NULL value will do the trick. | ||
412 | */ | ||
413 | strcpy(value, ""); | ||
414 | return; | ||
415 | } | 411 | } |
416 | 412 | ||
417 | memcpy(key, record[index].key, key_size); | 413 | memcpy(key, record[index].key, key_size); |
418 | memcpy(value, record[index].value, value_size); | 414 | memcpy(value, record[index].value, value_size); |
415 | return 0; | ||
419 | } | 416 | } |
420 | 417 | ||
421 | 418 | ||
@@ -494,21 +491,141 @@ done: | |||
494 | return; | 491 | return; |
495 | } | 492 | } |
496 | 493 | ||
494 | static void kvp_process_ipconfig_file(char *cmd, | ||
495 | char *config_buf, int len, | ||
496 | int element_size, int offset) | ||
497 | { | ||
498 | char buf[256]; | ||
499 | char *p; | ||
500 | char *x; | ||
501 | FILE *file; | ||
502 | |||
503 | /* | ||
504 | * First execute the command. | ||
505 | */ | ||
506 | file = popen(cmd, "r"); | ||
507 | if (file == NULL) | ||
508 | return; | ||
509 | |||
510 | if (offset == 0) | ||
511 | memset(config_buf, 0, len); | ||
512 | while ((p = fgets(buf, sizeof(buf), file)) != NULL) { | ||
513 | if ((len - strlen(config_buf)) < (element_size + 1)) | ||
514 | break; | ||
515 | |||
516 | x = strchr(p, '\n'); | ||
517 | *x = '\0'; | ||
518 | strcat(config_buf, p); | ||
519 | strcat(config_buf, ";"); | ||
520 | } | ||
521 | pclose(file); | ||
522 | } | ||
523 | |||
524 | static void kvp_get_ipconfig_info(char *if_name, | ||
525 | struct hv_kvp_ipaddr_value *buffer) | ||
526 | { | ||
527 | char cmd[512]; | ||
528 | |||
529 | /* | ||
530 | * Get the address of default gateway (ipv4). | ||
531 | */ | ||
532 | sprintf(cmd, "%s %s", "ip route show dev", if_name); | ||
533 | strcat(cmd, " | awk '/default/ {print $3 }'"); | ||
534 | |||
535 | /* | ||
536 | * Execute the command to gather gateway info. | ||
537 | */ | ||
538 | kvp_process_ipconfig_file(cmd, (char *)buffer->gate_way, | ||
539 | (MAX_GATEWAY_SIZE * 2), INET_ADDRSTRLEN, 0); | ||
540 | |||
541 | /* | ||
542 | * Get the address of default gateway (ipv6). | ||
543 | */ | ||
544 | sprintf(cmd, "%s %s", "ip -f inet6 route show dev", if_name); | ||
545 | strcat(cmd, " | awk '/default/ {print $3 }'"); | ||
546 | |||
547 | /* | ||
548 | * Execute the command to gather gateway info (ipv6). | ||
549 | */ | ||
550 | kvp_process_ipconfig_file(cmd, (char *)buffer->gate_way, | ||
551 | (MAX_GATEWAY_SIZE * 2), INET6_ADDRSTRLEN, 1); | ||
552 | |||
553 | } | ||
554 | |||
555 | |||
556 | static unsigned int hweight32(unsigned int *w) | ||
557 | { | ||
558 | unsigned int res = *w - ((*w >> 1) & 0x55555555); | ||
559 | res = (res & 0x33333333) + ((res >> 2) & 0x33333333); | ||
560 | res = (res + (res >> 4)) & 0x0F0F0F0F; | ||
561 | res = res + (res >> 8); | ||
562 | return (res + (res >> 16)) & 0x000000FF; | ||
563 | } | ||
564 | |||
565 | static int kvp_process_ip_address(void *addrp, | ||
566 | int family, char *buffer, | ||
567 | int length, int *offset) | ||
568 | { | ||
569 | struct sockaddr_in *addr; | ||
570 | struct sockaddr_in6 *addr6; | ||
571 | int addr_length; | ||
572 | char tmp[50]; | ||
573 | const char *str; | ||
574 | |||
575 | if (family == AF_INET) { | ||
576 | addr = (struct sockaddr_in *)addrp; | ||
577 | str = inet_ntop(family, &addr->sin_addr, tmp, 50); | ||
578 | addr_length = INET_ADDRSTRLEN; | ||
579 | } else { | ||
580 | addr6 = (struct sockaddr_in6 *)addrp; | ||
581 | str = inet_ntop(family, &addr6->sin6_addr.s6_addr, tmp, 50); | ||
582 | addr_length = INET6_ADDRSTRLEN; | ||
583 | } | ||
584 | |||
585 | if ((length - *offset) < addr_length + 1) | ||
586 | return 1; | ||
587 | if (str == NULL) { | ||
588 | strcpy(buffer, "inet_ntop failed\n"); | ||
589 | return 1; | ||
590 | } | ||
591 | if (*offset == 0) | ||
592 | strcpy(buffer, tmp); | ||
593 | else | ||
594 | strcat(buffer, tmp); | ||
595 | strcat(buffer, ";"); | ||
596 | |||
597 | *offset += strlen(str) + 1; | ||
598 | return 0; | ||
599 | } | ||
600 | |||
497 | static int | 601 | static int |
498 | kvp_get_ip_address(int family, char *buffer, int length) | 602 | kvp_get_ip_address(int family, char *if_name, int op, |
603 | void *out_buffer, int length) | ||
499 | { | 604 | { |
500 | struct ifaddrs *ifap; | 605 | struct ifaddrs *ifap; |
501 | struct ifaddrs *curp; | 606 | struct ifaddrs *curp; |
502 | int ipv4_len = strlen("255.255.255.255") + 1; | ||
503 | int ipv6_len = strlen("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")+1; | ||
504 | int offset = 0; | 607 | int offset = 0; |
505 | const char *str; | 608 | int sn_offset = 0; |
506 | char tmp[50]; | ||
507 | int error = 0; | 609 | int error = 0; |
508 | 610 | char *buffer; | |
611 | struct hv_kvp_ipaddr_value *ip_buffer; | ||
612 | char cidr_mask[5]; /* /xyz */ | ||
613 | int weight; | ||
614 | int i; | ||
615 | unsigned int *w; | ||
616 | char *sn_str; | ||
617 | struct sockaddr_in6 *addr6; | ||
618 | |||
619 | if (op == KVP_OP_ENUMERATE) { | ||
620 | buffer = out_buffer; | ||
621 | } else { | ||
622 | ip_buffer = out_buffer; | ||
623 | buffer = (char *)ip_buffer->ip_addr; | ||
624 | ip_buffer->addr_family = 0; | ||
625 | } | ||
509 | /* | 626 | /* |
510 | * On entry into this function, the buffer is capable of holding the | 627 | * On entry into this function, the buffer is capable of holding the |
511 | * maximum key value (2048 bytes). | 628 | * maximum key value. |
512 | */ | 629 | */ |
513 | 630 | ||
514 | if (getifaddrs(&ifap)) { | 631 | if (getifaddrs(&ifap)) { |
@@ -518,58 +635,99 @@ kvp_get_ip_address(int family, char *buffer, int length) | |||
518 | 635 | ||
519 | curp = ifap; | 636 | curp = ifap; |
520 | while (curp != NULL) { | 637 | while (curp != NULL) { |
521 | if ((curp->ifa_addr != NULL) && | 638 | if (curp->ifa_addr == NULL) { |
522 | (curp->ifa_addr->sa_family == family)) { | 639 | curp = curp->ifa_next; |
523 | if (family == AF_INET) { | 640 | continue; |
524 | struct sockaddr_in *addr = | 641 | } |
525 | (struct sockaddr_in *) curp->ifa_addr; | ||
526 | |||
527 | str = inet_ntop(family, &addr->sin_addr, | ||
528 | tmp, 50); | ||
529 | if (str == NULL) { | ||
530 | strcpy(buffer, "inet_ntop failed\n"); | ||
531 | error = 1; | ||
532 | goto getaddr_done; | ||
533 | } | ||
534 | if (offset == 0) | ||
535 | strcpy(buffer, tmp); | ||
536 | else | ||
537 | strcat(buffer, tmp); | ||
538 | strcat(buffer, ";"); | ||
539 | 642 | ||
540 | offset += strlen(str) + 1; | 643 | if ((if_name != NULL) && |
541 | if ((length - offset) < (ipv4_len + 1)) | 644 | (strncmp(curp->ifa_name, if_name, strlen(if_name)))) { |
542 | goto getaddr_done; | 645 | /* |
646 | * We want info about a specific interface; | ||
647 | * just continue. | ||
648 | */ | ||
649 | curp = curp->ifa_next; | ||
650 | continue; | ||
651 | } | ||
543 | 652 | ||
544 | } else { | 653 | /* |
654 | * We only support two address families: AF_INET and AF_INET6. | ||
655 | * If a family value of 0 is specified, we collect both | ||
656 | * supported address families; if not we gather info on | ||
657 | * the specified address family. | ||
658 | */ | ||
659 | if ((family != 0) && (curp->ifa_addr->sa_family != family)) { | ||
660 | curp = curp->ifa_next; | ||
661 | continue; | ||
662 | } | ||
663 | if ((curp->ifa_addr->sa_family != AF_INET) && | ||
664 | (curp->ifa_addr->sa_family != AF_INET6)) { | ||
665 | curp = curp->ifa_next; | ||
666 | continue; | ||
667 | } | ||
545 | 668 | ||
669 | if (op == KVP_OP_GET_IP_INFO) { | ||
546 | /* | 670 | /* |
547 | * We only support AF_INET and AF_INET6 | 671 | * Gather info other than the IP address. |
548 | * and the list of addresses is separated by a ";". | 672 | * IP address info will be gathered later. |
549 | */ | 673 | */ |
550 | struct sockaddr_in6 *addr = | 674 | if (curp->ifa_addr->sa_family == AF_INET) { |
551 | (struct sockaddr_in6 *) curp->ifa_addr; | 675 | ip_buffer->addr_family |= ADDR_FAMILY_IPV4; |
552 | 676 | /* | |
553 | str = inet_ntop(family, | 677 | * Get subnet info. |
554 | &addr->sin6_addr.s6_addr, | 678 | */ |
555 | tmp, 50); | 679 | error = kvp_process_ip_address( |
556 | if (str == NULL) { | 680 | curp->ifa_netmask, |
557 | strcpy(buffer, "inet_ntop failed\n"); | 681 | AF_INET, |
558 | error = 1; | 682 | (char *) |
559 | goto getaddr_done; | 683 | ip_buffer->sub_net, |
560 | } | 684 | length, |
561 | if (offset == 0) | 685 | &sn_offset); |
562 | strcpy(buffer, tmp); | 686 | if (error) |
563 | else | 687 | goto gather_ipaddr; |
564 | strcat(buffer, tmp); | 688 | } else { |
565 | strcat(buffer, ";"); | 689 | ip_buffer->addr_family |= ADDR_FAMILY_IPV6; |
566 | offset += strlen(str) + 1; | ||
567 | if ((length - offset) < (ipv6_len + 1)) | ||
568 | goto getaddr_done; | ||
569 | 690 | ||
691 | /* | ||
692 | * Get subnet info in CIDR format. | ||
693 | */ | ||
694 | weight = 0; | ||
695 | sn_str = (char *)ip_buffer->sub_net; | ||
696 | addr6 = (struct sockaddr_in6 *) | ||
697 | curp->ifa_netmask; | ||
698 | w = addr6->sin6_addr.s6_addr32; | ||
699 | |||
700 | for (i = 0; i < 4; i++) | ||
701 | weight += hweight32(&w[i]); | ||
702 | |||
703 | sprintf(cidr_mask, "/%d", weight); | ||
704 | if ((length - sn_offset) < | ||
705 | (strlen(cidr_mask) + 1)) | ||
706 | goto gather_ipaddr; | ||
707 | |||
708 | if (sn_offset == 0) | ||
709 | strcpy(sn_str, cidr_mask); | ||
710 | else | ||
711 | strcat(sn_str, cidr_mask); | ||
712 | strcat((char *)ip_buffer->sub_net, ";"); | ||
713 | sn_offset += strlen(sn_str) + 1; | ||
570 | } | 714 | } |
571 | 715 | ||
716 | /* | ||
717 | * Collect other ip related configuration info. | ||
718 | */ | ||
719 | |||
720 | kvp_get_ipconfig_info(if_name, ip_buffer); | ||
572 | } | 721 | } |
722 | |||
723 | gather_ipaddr: | ||
724 | error = kvp_process_ip_address(curp->ifa_addr, | ||
725 | curp->ifa_addr->sa_family, | ||
726 | buffer, | ||
727 | length, &offset); | ||
728 | if (error) | ||
729 | goto getaddr_done; | ||
730 | |||
573 | curp = curp->ifa_next; | 731 | curp = curp->ifa_next; |
574 | } | 732 | } |
575 | 733 | ||
@@ -646,6 +804,8 @@ int main(void) | |||
646 | char *p; | 804 | char *p; |
647 | char *key_value; | 805 | char *key_value; |
648 | char *key_name; | 806 | char *key_name; |
807 | int op; | ||
808 | int pool; | ||
649 | 809 | ||
650 | daemon(1, 0); | 810 | daemon(1, 0); |
651 | openlog("KVP", 0, LOG_USER); | 811 | openlog("KVP", 0, LOG_USER); |
@@ -687,7 +847,7 @@ int main(void) | |||
687 | message->id.val = CN_KVP_VAL; | 847 | message->id.val = CN_KVP_VAL; |
688 | 848 | ||
689 | hv_msg = (struct hv_kvp_msg *)message->data; | 849 | hv_msg = (struct hv_kvp_msg *)message->data; |
690 | hv_msg->kvp_hdr.operation = KVP_OP_REGISTER; | 850 | hv_msg->kvp_hdr.operation = KVP_OP_REGISTER1; |
691 | message->ack = 0; | 851 | message->ack = 0; |
692 | message->len = sizeof(struct hv_kvp_msg); | 852 | message->len = sizeof(struct hv_kvp_msg); |
693 | 853 | ||
@@ -721,12 +881,21 @@ int main(void) | |||
721 | incoming_cn_msg = (struct cn_msg *)NLMSG_DATA(incoming_msg); | 881 | incoming_cn_msg = (struct cn_msg *)NLMSG_DATA(incoming_msg); |
722 | hv_msg = (struct hv_kvp_msg *)incoming_cn_msg->data; | 882 | hv_msg = (struct hv_kvp_msg *)incoming_cn_msg->data; |
723 | 883 | ||
724 | switch (hv_msg->kvp_hdr.operation) { | 884 | /* |
725 | case KVP_OP_REGISTER: | 885 | * We will use the KVP header information to pass back |
886 | * the error from this daemon. So, first copy the state | ||
887 | * and set the error code to success. | ||
888 | */ | ||
889 | op = hv_msg->kvp_hdr.operation; | ||
890 | pool = hv_msg->kvp_hdr.pool; | ||
891 | hv_msg->error = HV_S_OK; | ||
892 | |||
893 | if ((in_hand_shake) && (op == KVP_OP_REGISTER1)) { | ||
726 | /* | 894 | /* |
727 | * Driver is registering with us; stash away the version | 895 | * Driver is registering with us; stash away the version |
728 | * information. | 896 | * information. |
729 | */ | 897 | */ |
898 | in_hand_shake = 0; | ||
730 | p = (char *)hv_msg->body.kvp_register.version; | 899 | p = (char *)hv_msg->body.kvp_register.version; |
731 | lic_version = malloc(strlen(p) + 1); | 900 | lic_version = malloc(strlen(p) + 1); |
732 | if (lic_version) { | 901 | if (lic_version) { |
@@ -737,44 +906,39 @@ int main(void) | |||
737 | syslog(LOG_ERR, "malloc failed"); | 906 | syslog(LOG_ERR, "malloc failed"); |
738 | } | 907 | } |
739 | continue; | 908 | continue; |
909 | } | ||
740 | 910 | ||
741 | /* | 911 | switch (op) { |
742 | * The current protocol with the kernel component uses a | ||
743 | * NULL key name to pass an error condition. | ||
744 | * For the SET, GET and DELETE operations, | ||
745 | * use the existing protocol to pass back error. | ||
746 | */ | ||
747 | |||
748 | case KVP_OP_SET: | 912 | case KVP_OP_SET: |
749 | if (kvp_key_add_or_modify(hv_msg->kvp_hdr.pool, | 913 | if (kvp_key_add_or_modify(pool, |
750 | hv_msg->body.kvp_set.data.key, | 914 | hv_msg->body.kvp_set.data.key, |
751 | hv_msg->body.kvp_set.data.key_size, | 915 | hv_msg->body.kvp_set.data.key_size, |
752 | hv_msg->body.kvp_set.data.value, | 916 | hv_msg->body.kvp_set.data.value, |
753 | hv_msg->body.kvp_set.data.value_size)) | 917 | hv_msg->body.kvp_set.data.value_size)) |
754 | strcpy(hv_msg->body.kvp_set.data.key, ""); | 918 | hv_msg->error = HV_S_CONT; |
755 | break; | 919 | break; |
756 | 920 | ||
757 | case KVP_OP_GET: | 921 | case KVP_OP_GET: |
758 | if (kvp_get_value(hv_msg->kvp_hdr.pool, | 922 | if (kvp_get_value(pool, |
759 | hv_msg->body.kvp_set.data.key, | 923 | hv_msg->body.kvp_set.data.key, |
760 | hv_msg->body.kvp_set.data.key_size, | 924 | hv_msg->body.kvp_set.data.key_size, |
761 | hv_msg->body.kvp_set.data.value, | 925 | hv_msg->body.kvp_set.data.value, |
762 | hv_msg->body.kvp_set.data.value_size)) | 926 | hv_msg->body.kvp_set.data.value_size)) |
763 | strcpy(hv_msg->body.kvp_set.data.key, ""); | 927 | hv_msg->error = HV_S_CONT; |
764 | break; | 928 | break; |
765 | 929 | ||
766 | case KVP_OP_DELETE: | 930 | case KVP_OP_DELETE: |
767 | if (kvp_key_delete(hv_msg->kvp_hdr.pool, | 931 | if (kvp_key_delete(pool, |
768 | hv_msg->body.kvp_delete.key, | 932 | hv_msg->body.kvp_delete.key, |
769 | hv_msg->body.kvp_delete.key_size)) | 933 | hv_msg->body.kvp_delete.key_size)) |
770 | strcpy(hv_msg->body.kvp_delete.key, ""); | 934 | hv_msg->error = HV_S_CONT; |
771 | break; | 935 | break; |
772 | 936 | ||
773 | default: | 937 | default: |
774 | break; | 938 | break; |
775 | } | 939 | } |
776 | 940 | ||
777 | if (hv_msg->kvp_hdr.operation != KVP_OP_ENUMERATE) | 941 | if (op != KVP_OP_ENUMERATE) |
778 | goto kvp_done; | 942 | goto kvp_done; |
779 | 943 | ||
780 | /* | 944 | /* |
@@ -782,13 +946,14 @@ int main(void) | |||
782 | * both the key and the value; if not read from the | 946 | * both the key and the value; if not read from the |
783 | * appropriate pool. | 947 | * appropriate pool. |
784 | */ | 948 | */ |
785 | if (hv_msg->kvp_hdr.pool != KVP_POOL_AUTO) { | 949 | if (pool != KVP_POOL_AUTO) { |
786 | kvp_pool_enumerate(hv_msg->kvp_hdr.pool, | 950 | if (kvp_pool_enumerate(pool, |
787 | hv_msg->body.kvp_enum_data.index, | 951 | hv_msg->body.kvp_enum_data.index, |
788 | hv_msg->body.kvp_enum_data.data.key, | 952 | hv_msg->body.kvp_enum_data.data.key, |
789 | HV_KVP_EXCHANGE_MAX_KEY_SIZE, | 953 | HV_KVP_EXCHANGE_MAX_KEY_SIZE, |
790 | hv_msg->body.kvp_enum_data.data.value, | 954 | hv_msg->body.kvp_enum_data.data.value, |
791 | HV_KVP_EXCHANGE_MAX_VALUE_SIZE); | 955 | HV_KVP_EXCHANGE_MAX_VALUE_SIZE)) |
956 | hv_msg->error = HV_S_CONT; | ||
792 | goto kvp_done; | 957 | goto kvp_done; |
793 | } | 958 | } |
794 | 959 | ||
@@ -807,13 +972,13 @@ int main(void) | |||
807 | strcpy(key_value, lic_version); | 972 | strcpy(key_value, lic_version); |
808 | break; | 973 | break; |
809 | case NetworkAddressIPv4: | 974 | case NetworkAddressIPv4: |
810 | kvp_get_ip_address(AF_INET, key_value, | 975 | kvp_get_ip_address(AF_INET, NULL, KVP_OP_ENUMERATE, |
811 | HV_KVP_EXCHANGE_MAX_VALUE_SIZE); | 976 | key_value, HV_KVP_EXCHANGE_MAX_VALUE_SIZE); |
812 | strcpy(key_name, "NetworkAddressIPv4"); | 977 | strcpy(key_name, "NetworkAddressIPv4"); |
813 | break; | 978 | break; |
814 | case NetworkAddressIPv6: | 979 | case NetworkAddressIPv6: |
815 | kvp_get_ip_address(AF_INET6, key_value, | 980 | kvp_get_ip_address(AF_INET6, NULL, KVP_OP_ENUMERATE, |
816 | HV_KVP_EXCHANGE_MAX_VALUE_SIZE); | 981 | key_value, HV_KVP_EXCHANGE_MAX_VALUE_SIZE); |
817 | strcpy(key_name, "NetworkAddressIPv6"); | 982 | strcpy(key_name, "NetworkAddressIPv6"); |
818 | break; | 983 | break; |
819 | case OSBuildNumber: | 984 | case OSBuildNumber: |
@@ -841,11 +1006,7 @@ int main(void) | |||
841 | strcpy(key_name, "ProcessorArchitecture"); | 1006 | strcpy(key_name, "ProcessorArchitecture"); |
842 | break; | 1007 | break; |
843 | default: | 1008 | default: |
844 | strcpy(key_value, "Unknown Key"); | 1009 | hv_msg->error = HV_S_CONT; |
845 | /* | ||
846 | * We use a null key name to terminate enumeration. | ||
847 | */ | ||
848 | strcpy(key_name, ""); | ||
849 | break; | 1010 | break; |
850 | } | 1011 | } |
851 | /* | 1012 | /* |