diff options
Diffstat (limited to 'btif/src/btif_config_util.cpp')
-rw-r--r-- | btif/src/btif_config_util.cpp | 702 |
1 files changed, 702 insertions, 0 deletions
diff --git a/btif/src/btif_config_util.cpp b/btif/src/btif_config_util.cpp new file mode 100644 index 0000000..0d32da1 --- /dev/null +++ b/btif/src/btif_config_util.cpp @@ -0,0 +1,702 @@ +/************************************************************************************ + * + * Copyright (C) 2009-2011 Broadcom Corporation + * + * This program is the proprietary software of Broadcom Corporation and/or its + * licensors, and may only be used, duplicated, modified or distributed + * pursuant to the terms and conditions of a separate, written license + * agreement executed between you and Broadcom (an "Authorized License"). + * Except as set forth in an Authorized License, Broadcom grants no license + * (express or implied), right to use, or waiver of any kind with respect to + * the Software, and Broadcom expressly reserves all rights in and to the + * Software and all intellectual property rights therein. + * IF YOU HAVE NO AUTHORIZED LICENSE, THEN YOU HAVE NO RIGHT TO USE THIS + * SOFTWARE IN ANY WAY, AND SHOULD IMMEDIATELY NOTIFY BROADCOM AND DISCONTINUE + * ALL USE OF THE SOFTWARE. + * + * Except as expressly set forth in the Authorized License, + * + * 1. This program, including its structure, sequence and organization, + * constitutes the valuable trade secrets of Broadcom, and you shall + * use all reasonable efforts to protect the confidentiality thereof, + * and to use this information only in connection with your use of + * Broadcom integrated circuit products. + * + * 2. TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED + * "AS IS" AND WITH ALL FAULTS AND BROADCOM MAKES NO PROMISES, + * REPRESENTATIONS OR WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, + * OR OTHERWISE, WITH RESPECT TO THE SOFTWARE. BROADCOM SPECIFICALLY + * DISCLAIMS ANY AND ALL IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, + * NONINFRINGEMENT, FITNESS FOR A PARTICULAR PURPOSE, LACK OF VIRUSES, + * ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, QUIET POSSESSION OR + * CORRESPONDENCE TO DESCRIPTION. YOU ASSUME THE ENTIRE RISK ARISING OUT + * OF USE OR PERFORMANCE OF THE SOFTWARE. + * + * 3. TO THE MAXIMUM EXTENT PERMITTED BY LAW, IN NO EVENT SHALL BROADCOM OR + * ITS LICENSORS BE LIABLE FOR + * (i) CONSEQUENTIAL, INCIDENTAL, SPECIAL, INDIRECT, OR EXEMPLARY + * DAMAGES WHATSOEVER ARISING OUT OF OR IN ANY WAY RELATING TO + * YOUR USE OF OR INABILITY TO USE THE SOFTWARE EVEN IF BROADCOM + * HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES; OR + * (ii) ANY AMOUNT IN EXCESS OF THE AMOUNT ACTUALLY PAID FOR THE + * SOFTWARE ITSELF OR U.S. $1, WHICHEVER IS GREATER. THESE + * LIMITATIONS SHALL APPLY NOTWITHSTANDING ANY FAILURE OF + * ESSENTIAL PURPOSE OF ANY LIMITED REMEDY. + * + ************************************************************************************/ +#include <stdio.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/statfs.h> +#include <sys/vfs.h> +#include <unistd.h> +#include <dirent.h> +#include <limits.h> +#include <sys/file.h> +#include <sys/mman.h> + +#include "btif_config.h" +#include "btif_config_util.h" +#ifndef ANDROID_NDK +#define ANDROID_NDK +#endif +#include "tinyxml2.h" +#ifndef FALSE +#define TRUE 1 +#define FALSE 0 +#endif +#define LOG_TAG "btif_config_util" +extern "C" { +#include "btif_sock_util.h" +} +#include <stdlib.h> +#include <cutils/log.h> +#define info(fmt, ...) ALOGI ("%s(L%d): " fmt,__FUNCTION__, __LINE__, ## __VA_ARGS__) +#define debug(fmt, ...) ALOGD ("%s(L%d): " fmt,__FUNCTION__, __LINE__, ## __VA_ARGS__) +#define warn(fmt, ...) ALOGW ("## WARNING : %s(L%d): " fmt "##",__FUNCTION__, __LINE__, ## __VA_ARGS__) +#define error(fmt, ...) ALOGE ("## ERROR : %s(L%d): " fmt "##",__FUNCTION__, __LINE__, ## __VA_ARGS__) +#define asrt(s) if(!(s)) ALOGE ("## %s assert %s failed at line:%d ##",__FUNCTION__, #s, __LINE__) + +#define BLUEDROID_ROOT "Bluedroid" +#define BLUEDROID_NAME_TAG "Tag" +#define BLUEDROID_VALUE_TYPE "Type" +#define BLUEDROID_TAG_REMOTE_DEVICE "Remote Devices" + +using namespace tinyxml2; +struct enum_user_data +{ + const char* sn; //current section name + const char* kn; //current key name + const char* vn; //current value name + int si, ki, vi; + XMLDocument* xml; + XMLElement* se; + XMLElement* ke; + XMLElement* ve; +}; + + +static int type_str2int(const char* type); +static const char* type_int2str(int type); +static inline void create_ele_name(int index, char* element, int len); +static inline int validate_ele_name(const char* key); +static int parse_sections(const char* section_name, const XMLElement* section); +static void enum_config(void* user_data, const char* section, const char* key, const char* name, + const char* value, int bytes, int type); +static inline void bytes2hex(const char* data, int bytes, char* str) +{ + static const char* hex_table = "0123456789abcdef"; + for(int i = 0; i < bytes; i++) + { + *str = hex_table[(data[i] >> 4) & 0xf]; + ++str; + *str = hex_table[data[i] & 0xf]; + ++str; + } + *str = 0; +} +static inline int hex2byte(char hex) +{ + if('0' <= hex && hex <= '9') + return hex - '0'; + if('a' <= hex && hex <= 'z') + return hex - 'a' + 0xa; + if('A' <= hex && hex <= 'Z') + return hex - 'A' + 0xa; + return -1; +} +static inline int trim_bin_str_value(const char** str) +{ + while(**str == ' ' || **str == '\r' || **str == '\t' || **str == '\n') + (*str)++; + int len = 0; + const char* s = *str; + while(*s && *s != ' ' && *s != '\r' && *s != '\t' && *s != '\n') + { + len++; + s++; + } + return len; +} +static inline bool hex2bytes(const char* str, int len, char* data) +{ + if(len % 2) + { + error("cannot convert odd len hex str: %s, len:%d to binary", str, len); + return false; + } + for(int i = 0; i < len; i+= 2) + { + int d = hex2byte(str[i]); + if(d < 0) + { + error("cannot convert hex: %s, len:%d to binary", str, len); + return false; + } + *data = (char)(d << 4); + d = hex2byte(str[i+1]); + if(d < 0) + { + error("cannot convert hex: %s, len:%d to binary", str, len); + return false; + } + *data++ |= (char)d; + } + return true; +} +static inline void reverse_bin(char *bin, int size) +{ + for(int i = 0; i < size /2; i++) + { + int b = bin[i]; + bin[i] = bin[size - i - 1]; + bin[size -i - 1] = b; + } +} +//////////////////////////////////////////////////////////////////////////////////////////////////////// +int btif_config_save_file(const char* file_name) +{ + debug("in file name:%s", file_name); + XMLDocument xml; + XMLElement* root = xml.NewElement(BLUEDROID_ROOT); + xml.InsertFirstChild(root); + int ret = FALSE; + enum_user_data data; + memset(&data, 0, sizeof(data)); + data.xml = &xml; + if(btif_config_enum(enum_config, &data)) + ret = xml.SaveFile(file_name) == XML_SUCCESS; + return ret; +} +int btif_config_load_file(const char* file_name) +{ + //if(access(file_name, 0) != 0) + // return XML_ERROR_FILE_NOT_FOUND; + XMLDocument xml; + int err = xml.LoadFile(file_name); + const XMLElement* root = xml.RootElement(); + int ret = FALSE; + if(err == XML_SUCCESS && root && strcmp(root->Name(), BLUEDROID_ROOT) == 0) + { + const XMLElement* section; + for(section = root->FirstChildElement(); section; section = section->NextSiblingElement()) + { + //debug("section tag:%s", section->Name()); + if(validate_ele_name(section->Name())) + { + const char* section_name = section->Attribute(BLUEDROID_NAME_TAG); + if(section_name && *section_name) + if(parse_sections(section_name, section)) + ret = TRUE; + } + } + } + return ret; +} +////////////////////////////////////////////////////////////////////////////////////////////////////////// +static int parse_sections(const char* section_name, const XMLElement* section) +{ + const XMLElement* key; + //debug("in"); + for(key = section->FirstChildElement(); key; key = key->NextSiblingElement()) + { + //debug("key tag:%s", key->Name()); + if(validate_ele_name(key->Name())) + { + const char* key_name = key->Attribute(BLUEDROID_NAME_TAG); + //debug("key name:%s", key_name); + if(key_name && *key_name) + { + const XMLElement* value; + for(value = key->FirstChildElement(); value; value = value->NextSiblingElement()) + { + const char* value_name = value->Attribute(BLUEDROID_NAME_TAG); + const char* value_type = value->Attribute(BLUEDROID_VALUE_TYPE); + //debug("value ele name:%s, section name:%s, key name:%s, value name:%s, value type:%s", + // value->Name(), section_name, key_name, value_name, value_type); + int type = type_str2int(value_type); + if(value_name && *value_name && type != BTIF_CFG_TYPE_INVALID) + { + const char* value_str = value->GetText() ? value->GetText() : ""; + //debug("value_name:%s, value_str:%s, value_type:%s, type:%x", + // value_name, value_str, value_type, type); + if(type & BTIF_CFG_TYPE_STR) + btif_config_set_str(section_name, key_name, value_name, value_str); + else if(type & BTIF_CFG_TYPE_INT) + { + if(*value_str) + { + int v = atoi(value_str); + btif_config_set_int(section_name, key_name, value_name, v); + } + } + else if(type & BTIF_CFG_TYPE_BIN) + { + int len = trim_bin_str_value(&value_str); + if(len > 0 && len % 2 == 0) + { + char *bin = (char*)alloca(len / 2); + if(hex2bytes(value_str, len, bin)) + btif_config_set(section_name, key_name, value_name, bin, len/2, BTIF_CFG_TYPE_BIN); + } + } + else error("Unsupported value:%s, type:%s not loaded", value_name, value_type); + } + } + } + } + } + //debug("out"); + return TRUE; +} +static inline XMLElement* add_ele(XMLDocument* xml, XMLElement* p, int index, + const char* name_tag, const char* value_type = NULL) +{ + //debug("in, tag:%s", name_tag); + char ele_name[128] = {0}; + create_ele_name(index, ele_name, sizeof(ele_name)); + XMLElement* ele = xml->NewElement(ele_name); + //debug("ele name:%s, tag:%s, index:%d, value type:%s", ele_name, name_tag, index, value_type); + ele->SetAttribute(BLUEDROID_NAME_TAG, name_tag); + if(value_type && *value_type) + ele->SetAttribute(BLUEDROID_VALUE_TYPE, value_type); + p->InsertEndChild(ele); + //debug("out, tag:%s", name_tag); + return ele; +} +static void enum_config(void* user_data, const char* section_name, const char* key_name, const char* value_name, + const char* value, int bytes, int type) +{ + enum_user_data& d = *(enum_user_data*)user_data; + //debug("in, key:%s, value:%s", key_name, value_name); + //debug("section name:%s, key name:%s, value name:%s, value type:%s", + // section_name, key_name, value_name, type_int2str(type)); + if(type & BTIF_CFG_TYPE_VOLATILE) + return; //skip any volatile value + if(d.sn != section_name) + { + d.sn = section_name; + d.se = add_ele(d.xml, d.xml->RootElement(), ++d.si, section_name); + d.ki = 0; + } + if(d.kn != key_name) + { + d.kn = key_name; + d.ke = add_ele(d.xml, d.se, ++d.ki, key_name); + d.vi = 0; + } + if(d.vn != value_name) + { + if(type & BTIF_CFG_TYPE_STR) + { + d.vn = value_name; + d.ve = add_ele(d.xml, d.ke, ++d.vi, value_name, type_int2str(type)); + d.ve->InsertFirstChild(d.xml->NewText(value)); + } + else if(type & BTIF_CFG_TYPE_INT) + { + d.vn = value_name; + d.ve = add_ele(d.xml, d.ke, ++d.vi, value_name, type_int2str(type)); + char value_str[64] = {0}; + snprintf(value_str, sizeof(value_str), "%d", *(int*)value); + d.ve->InsertFirstChild(d.xml->NewText(value_str)); + } + else if(type & BTIF_CFG_TYPE_BIN) + { + d.vn = value_name; + d.ve = add_ele(d.xml, d.ke, ++d.vi, value_name, type_int2str(type)); + char* value_str = (char*)alloca(bytes*2 + 1); + bytes2hex(value, bytes, value_str); + d.ve->InsertFirstChild(d.xml->NewText(value_str)); + } + else error("unsupported config value name:%s, type:%s not saved", d.vn, type_int2str(type)); + } + //debug("out, key:%s, value:%s", key_name, value_name); +} + +static int type_str2int(const char* type) +{ + if(strcmp(type, "int") == 0) + return BTIF_CFG_TYPE_INT; + if(strcmp(type, "binary") == 0) + return BTIF_CFG_TYPE_BIN; + if(type == 0 || *type == 0 || strcmp(type, "string") == 0) + return BTIF_CFG_TYPE_STR; + error("unknown value type:%s", type); + return BTIF_CFG_TYPE_INVALID; +} +static const char* type_int2str(int type) +{ + switch(type) + { + case BTIF_CFG_TYPE_INT: + return "int"; + case BTIF_CFG_TYPE_BIN: + return "binary"; + case BTIF_CFG_TYPE_STR: + return "string"; + default: + error("unknown type:%d", type); + break; + } + return NULL; +} + +static inline void create_ele_name(int index, char* element, int len) +{ + snprintf(element, len, "N%d", index); +} +static inline int validate_ele_name(const char* key) +{ + //must be 'N' followed with numbers + if(key && *key == 'N' && *++key) + { + while(*key) + { + if(*key < '0' || *key > '9') + return FALSE; + ++key; + } + return TRUE; + } + return FALSE; +} +static int open_file_map(const char *pathname, const char**map, int* size) +{ + struct stat st; + st.st_size = 0; + int fd; + //debug("in"); + if((fd = open(pathname, O_RDONLY)) >= 0) + { + //debug("fd:%d", fd); + if(fstat(fd, &st) == 0 && st.st_size) + { + *size = st.st_size; + *map = (const char*)mmap(NULL, *size, PROT_READ, MAP_SHARED, fd, 0); + if(*map && *map != MAP_FAILED) + { + //debug("out map:%p, size:%d", *map, *size); + return fd; + } + } + close(fd); + } + //debug("out, failed"); + return -1; +} +static void close_file_map(int fd, const char* map, int size) +{ + munmap((void*)map, size); + close(fd); +} +static int read_file_line(const char* map, int start_pos, int size, int* line_size) +{ + *line_size = 0; + //debug("in, start pos:%d, size:%d", start_pos, size); + int i; + for(i = start_pos; i < size; i++) + { + ++*line_size; + if(map[i] == '\r' || map[i] == '\n') + break; + } + //debug("out, ret:%d, start pos:%d, size:%d, line_size:%d", i, start_pos, size, *line_size); + return i + 1; +} +static const char* find_value_line(const char* map, int size, const char *key, int* value_size) +{ + int key_len = strlen(key); + int i; + for(i = 0; i < size; i++) + { + if(map[i] == *key) + { + if(i + key_len + 1 > size) + return NULL; + if(memcmp(map + i, key, key_len) == 0) + { + read_file_line(map, i + key_len + 1, size, value_size); + if(*value_size) + return map + i + key_len + 1; + break; + } + } + } + return NULL; +} +static int read_line_word(const char* line, int start_pos, int line_size, char* word, int *word_size, bool lower_case = false) +{ + int i; + //skip space + //debug("in, line start_pos:%d, line_size:%d", start_pos, line_size); + for(i = start_pos; i < line_size; i++) + { + //debug("skip space loop, line[%d]:%c", i, line[i]); + if(line[i] != ' ' && line[i] != '\t' && line[i] != '\r' && line[i] !='\n') + break; + } + *word_size = 0; + for(; i < line_size; i++) + { + //debug("add word loop, line[%d]:%c", i, line[i]); + if(line[i] != ' ' && line[i] != '\t' && line[i] != '\r' && line[i] !='\n') + { + ++*word_size; + if(lower_case && 'A' <= line[i] && line[i] <= 'Z') + *word++ = 'a' - 'A' + line[i]; + else + *word++ = line[i]; + } + else break; + } + *word = 0; + //debug("out, ret:%d, word:%s, word_size:%d, line start_pos:%d, line_size:%d", + // i, word, *word_size, start_pos, line_size); + return i; +} +static int is_valid_bd_addr(const char* addr) +{ + int len = strlen(addr); + //debug("addr: %s, len:%d", addr, len); + return len == 17 && addr[2] == ':' && addr[5] == ':' && addr[14] == ':'; +} +static int load_bluez_cfg_value(const char* adapter_path, const char* file_name) +{ + //debug("in"); + + const char* map = NULL; + int size = 0; + int ret = FALSE; + char path[256]; + snprintf(path, sizeof(path), "%s/%s", adapter_path, file_name); + int fd = open_file_map(path, &map, &size); + //debug("in, path:%s, fd:%d, size:%d", path, fd, size); + if(fd < 0 || size == 0) + { + error("open_file_map fail, fd:%d, path:%s, size:%d", fd, path, size); + //debug("out"); + return FALSE; + } + //get local bt device name from bluez config + int line_size = 0; + const char *value_line = find_value_line(map, size, "name", &line_size); + if(value_line && line_size > 0) + { + char value[line_size + 1]; + memcpy(value, value_line, line_size); + value[line_size] = 0; + //debug("import local bt dev names:%s", value); + btif_config_set_str("Local", "Adapter", "Name", value); + ret = TRUE; + } + + close_file_map(fd, map, size); + //debug("out, ret:%d", ret); + return ret; +} + +int load_bluez_adapter_info(char* adapter_path, int size) +{ + struct dirent *dptr; + DIR *dirp; + int ret = FALSE; + if((dirp = opendir(BLUEZ_PATH)) != NULL) + { + while((dptr = readdir(dirp)) != NULL) + { + //debug("readdir: %s",dptr->d_name); + if(is_valid_bd_addr(dptr->d_name)) + { + snprintf(adapter_path, size, "%s%s", BLUEZ_PATH, dptr->d_name); + btif_config_set_str("Local", "Adapter", "Address", dptr->d_name); + load_bluez_cfg_value(adapter_path, BLUEZ_CONFIG); + ret = TRUE; + break; + } + } + closedir(dirp); + } + return ret; +} +static inline void upcase_addr(const char* laddr, char* uaddr, int size) +{ + int i; + for(i = 0; i < size && laddr[i]; i++) + uaddr[i] = ('a' <= laddr[i] && laddr[i] <= 'z') ? + laddr[i] - ('a' - 'A') : laddr[i]; + uaddr[i] = 0; +} +static int load_bluez_dev_value(const char* adapter_path, const char* bd_addr, + const char* file_name, const char* cfg_value_name, int type) +{ + //debug("in"); + char addr[32]; + upcase_addr(bd_addr, addr, sizeof(addr)); + + const char* map = NULL; + int size = 0; + int ret = FALSE; + char path[256]; + snprintf(path, sizeof(path), "%s/%s", adapter_path, file_name); + int fd = open_file_map(path, &map, &size); + //debug("in, path:%s, addr:%s, fd:%d, size:%d", path, addr, fd, size); + if(fd < 0 || size == 0) + { + error("open_file_map fail, fd:%d, path:%s, size:%d", fd, path, size); + //debug("out"); + return FALSE; + } + int line_size = 0; + const char *value_line = find_value_line(map, size, addr, &line_size); + if(value_line && line_size) + { + char line[line_size + 1]; + memcpy(line, value_line, line_size); + line[line_size] = 0; + //debug("addr:%s, Names:%s", bd_addr, line); + if(type == BTIF_CFG_TYPE_STR) + btif_config_set_str("Remote", bd_addr, cfg_value_name, line); + else if(type == BTIF_CFG_TYPE_INT) + { + int v = strtol(line, NULL, 16); + //filter out unspported devices by its class + if(strcmp(file_name, BLUEZ_CLASSES) == 0) + { + switch((v & 0x1f00) >> 8) + { + case 0x5: //hid device + error("skip paired hid devices"); + close_file_map(fd, map, size); + return FALSE; + } + } + btif_config_set_int("Remote", bd_addr, cfg_value_name, v); + } + ret = TRUE; + } + close_file_map(fd, map, size); + //debug("out, ret:%d", ret); + return ret; +} +static inline int bz2bd_linkkeytype(int type) +{ +#if 1 + return type; +#else + int table[5] = {0, 0, 0, 0, 0}; + if(0 <= type && type < (int)(sizeof(table)/sizeof(int))) + return table[type]; + return 0; +#endif +} +int load_bluez_linkkeys(const char* adapter_path) +{ + const char* map = NULL; + int size = 0; + int ret = FALSE; + char path[256]; + //debug("in"); + snprintf(path, sizeof(path), "%s/%s", adapter_path, BLUEZ_LINKKEY); + int fd = open_file_map(path, &map, &size); + if(fd < 0 || size == 0) + { + error("open_file_map fail, fd:%d, path:%s, size:%d", fd, path, size); + //debug("out"); + return FALSE; + } + int pos = 0; + //debug("path:%s, size:%d", path, size); + while(pos < size) + { + int line_size = 0; + int next_pos = read_file_line(map, pos, size, &line_size); + //debug("pos:%d, next_pos:%d, size:%d, line_size:%d", pos, next_pos, size, line_size); + if(line_size) + { + const char* line = map + pos; + char addr[line_size + 1]; + int word_pos = 0; + int addr_size = 0; + word_pos = read_line_word(line, word_pos, line_size, addr, &addr_size, true); + //debug("read_line_word addr:%s, addr_size:%d", addr, addr_size); + if(*addr) + { + char value[line_size + 1]; + int value_size = 0; + //read link key + word_pos = read_line_word(line, word_pos, line_size, value, &value_size); + //debug("read_line_word linkkey:%s, size:%d", value, value_size); + if(*value) + { + int linkkey_size = value_size / 2; + char linkkey[linkkey_size]; + if(hex2bytes(value, value_size, linkkey)) + { //read link key type + //bluez save the linkkey in reversed order + reverse_bin(linkkey, linkkey_size); + word_pos = read_line_word(line, word_pos, + line_size, value, &value_size); + if(*value) + { + if(load_bluez_dev_value(adapter_path, addr, + BLUEZ_CLASSES, "DevClass", BTIF_CFG_TYPE_INT) && + load_bluez_dev_value(adapter_path, addr, + BLUEZ_NAMES, "Name", BTIF_CFG_TYPE_STR) && + load_bluez_dev_value(adapter_path, addr, + BLUEZ_TYPES, "DevType", BTIF_CFG_TYPE_INT) && + load_bluez_dev_value(adapter_path, addr, + BLUEZ_PROFILES, "Service", BTIF_CFG_TYPE_STR)) + { + load_bluez_dev_value(adapter_path, addr, + BLUEZ_ALIASES, "Aliase", BTIF_CFG_TYPE_STR); + int key_type = bz2bd_linkkeytype(atoi(value)); + + //read pin len + word_pos = read_line_word(line, word_pos, line_size, value, &value_size); + if(*value) + { + int pin_len = atoi(value); + ret = TRUE; + btif_config_set("Remote", addr, "LinkKey", linkkey, + linkkey_size, BTIF_CFG_TYPE_BIN); + //dump_bin("import bluez linkkey", linkkey, linkkey_size); + btif_config_set_int("Remote", addr, "LinkKeyType", key_type); + btif_config_set_int("Remote", addr, "PinLength", pin_len); + } + } + } + } + } + } + } + //debug("pos:%d, next_pos:%d, size:%d, line_size:%d", pos, next_pos, size, line_size); + pos = next_pos; + } + close_file_map(fd, map, size); + //debug("out, ret:%d", ret); + return ret; +} + |