Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
OpenHarmony
Startup Init Lite
提交
c485583a
S
Startup Init Lite
项目概览
OpenHarmony
/
Startup Init Lite
接近 2 年 前同步成功
通知
3
Star
37
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
S
Startup Init Lite
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
未验证
提交
c485583a
编写于
1月 31, 2023
作者:
O
openharmony_ci
提交者:
Gitee
1月 31, 2023
浏览文件
操作
浏览文件
下载
差异文件
!1727 添加接口:CachedParameterGet
Merge pull request !1727 from cheng_jinsong/0129
上级
45b7ba2b
01524182
变更
7
隐藏空白更改
内联
并排
Showing
7 changed file
with
233 addition
and
26 deletion
+233
-26
services/include/param/sys_param.h
services/include/param/sys_param.h
+22
-0
services/param/base/param_base.c
services/param/base/param_base.c
+130
-0
services/param/base/param_base.h
services/param/base/param_base.h
+11
-0
services/param/base/param_comm.c
services/param/base/param_comm.c
+0
-25
services/param/include/param_manager.h
services/param/include/param_manager.h
+0
-1
test/moduletest/param_test_cmds.c
test/moduletest/param_test_cmds.c
+44
-0
test/unittest/param/param_unittest.cpp
test/unittest/param/param_unittest.cpp
+26
-0
未找到文件。
services/include/param/sys_param.h
浏览文件 @
c485583a
...
@@ -24,6 +24,7 @@ extern "C" {
...
@@ -24,6 +24,7 @@ extern "C" {
#endif
#endif
typedef
uint32_t
ParamHandle
;
typedef
uint32_t
ParamHandle
;
typedef
void
*
CachedHandle
;
typedef
struct
{
typedef
struct
{
uint8_t
updaterMode
;
uint8_t
updaterMode
;
...
@@ -72,6 +73,27 @@ int SystemGetParameterValue(ParamHandle handle, char *value, unsigned int *len);
...
@@ -72,6 +73,27 @@ int SystemGetParameterValue(ParamHandle handle, char *value, unsigned int *len);
long
long
GetSystemCommitId
(
void
);
long
long
GetSystemCommitId
(
void
);
/**
* 外部接口
* 保存相关的parameter信息,包括workspace,和各层的commit。
*
*/
CachedHandle
CachedParameterCreate
(
const
char
*
name
,
const
char
*
defValue
);
/**
* 外部接口
* 如果获取到value,返回对应的 paramValue的指针,否则返回上面定义的defValue
*
*/
const
char
*
CachedParameterGet
(
CachedHandle
handle
);
/**
* 外部接口
* 释放handle内存
*
*/
void
CachedParameterDestroy
(
CachedHandle
handle
);
#ifdef __cplusplus
#ifdef __cplusplus
#if __cplusplus
#if __cplusplus
}
}
...
...
services/param/base/param_base.c
浏览文件 @
c485583a
...
@@ -20,6 +20,8 @@
...
@@ -20,6 +20,8 @@
#include "param_manager.h"
#include "param_manager.h"
#include "param_trie.h"
#include "param_trie.h"
static
int
ReadParamValue
(
ParamHandle
handle
,
char
*
value
,
uint32_t
*
length
);
static
ParamWorkSpace
g_paramWorkSpace
=
{
0
};
static
ParamWorkSpace
g_paramWorkSpace
=
{
0
};
PARAM_STATIC
int
WorkSpaceNodeCompare
(
const
HashNode
*
node1
,
const
HashNode
*
node2
)
PARAM_STATIC
int
WorkSpaceNodeCompare
(
const
HashNode
*
node1
,
const
HashNode
*
node2
)
{
{
...
@@ -329,4 +331,132 @@ int SystemGetParameterValue(ParamHandle handle, char *value, unsigned int *len)
...
@@ -329,4 +331,132 @@ int SystemGetParameterValue(ParamHandle handle, char *value, unsigned int *len)
{
{
PARAM_CHECK
(
len
!=
NULL
&&
handle
!=
0
,
return
-
1
,
"The value is null"
);
PARAM_CHECK
(
len
!=
NULL
&&
handle
!=
0
,
return
-
1
,
"The value is null"
);
return
ReadParamValue
(
handle
,
value
,
len
);
return
ReadParamValue
(
handle
,
value
,
len
);
}
static
int
ReadParamValue_
(
ParamNode
*
entry
,
uint32_t
*
commitId
,
char
*
value
,
uint32_t
*
length
)
{
uint32_t
id
=
*
commitId
;
do
{
*
commitId
=
id
;
int
ret
=
ParamMemcpy
(
value
,
*
length
,
entry
->
data
+
entry
->
keyLength
+
1
,
entry
->
valueLength
);
PARAM_CHECK
(
ret
==
0
,
return
-
1
,
"Failed to copy value"
);
value
[
entry
->
valueLength
]
=
'\0'
;
*
length
=
entry
->
valueLength
;
id
=
ReadCommitId
(
entry
);
}
while
(
*
commitId
!=
id
);
// if change,must read
return
0
;
}
static
int
ReadParamValue
(
ParamHandle
handle
,
char
*
value
,
uint32_t
*
length
)
{
ParamWorkSpace
*
paramSpace
=
GetParamWorkSpace
();
PARAM_CHECK
(
paramSpace
!=
NULL
,
return
-
1
,
"Invalid workspace"
);
PARAM_CHECK
(
length
!=
NULL
,
return
PARAM_CODE_INVALID_PARAM
,
"Invalid param"
);
ParamNode
*
entry
=
(
ParamNode
*
)
GetTrieNodeByHandle
(
handle
);
if
(
entry
==
NULL
)
{
return
PARAM_CODE_NOT_FOUND
;
}
if
(
value
==
NULL
)
{
*
length
=
entry
->
valueLength
+
1
;
return
0
;
}
PARAM_CHECK
(
*
length
>
entry
->
valueLength
,
return
PARAM_CODE_INVALID_PARAM
,
"Invalid value len %u %u"
,
*
length
,
entry
->
valueLength
);
uint32_t
commitId
=
ReadCommitId
(
entry
);
return
ReadParamValue_
(
entry
,
&
commitId
,
value
,
length
);
}
CachedHandle
CachedParameterCreate
(
const
char
*
name
,
const
char
*
defValue
)
{
PARAM_CHECK
(
name
!=
NULL
&&
defValue
!=
NULL
,
return
NULL
,
"Invalid name or default value"
);
PARAM_WORKSPACE_CHECK
(
GetParamWorkSpace
(),
return
NULL
,
"Invalid param workspace"
);
uint32_t
nameLen
=
strlen
(
name
);
PARAM_CHECK
(
nameLen
<
PARAM_NAME_LEN_MAX
,
return
NULL
,
"Invalid name %s"
,
name
);
uint32_t
valueLen
=
strlen
(
defValue
);
if
(
IS_READY_ONLY
(
name
))
{
PARAM_CHECK
(
valueLen
<
PARAM_CONST_VALUE_LEN_MAX
,
return
NULL
,
"Illegal param value %s"
,
defValue
);
}
else
{
PARAM_CHECK
(
valueLen
<
PARAM_VALUE_LEN_MAX
,
return
NULL
,
"Illegal param value %s length"
,
defValue
);
}
int
ret
=
CheckParamPermission
(
GetParamSecurityLabel
(),
name
,
DAC_READ
);
PARAM_CHECK
(
ret
==
0
,
return
NULL
,
"Forbid to access parameter %s"
,
name
);
WorkSpace
*
workspace
=
GetWorkSpace
(
name
);
PARAM_CHECK
(
workspace
!=
NULL
,
return
NULL
,
"Invalid workSpace"
);
ParamTrieNode
*
node
=
FindTrieNode
(
workspace
,
name
,
strlen
(
name
),
NULL
);
CachedParameter
*
param
=
(
CachedParameter
*
)
malloc
(
sizeof
(
CachedParameter
)
+
PARAM_ALIGN
(
nameLen
)
+
1
+
PARAM_VALUE_LEN_MAX
);
PARAM_CHECK
(
param
!=
NULL
,
return
NULL
,
"Failed to create CachedParameter for %s"
,
name
);
ret
=
ParamStrCpy
(
param
->
data
,
nameLen
+
1
,
name
);
PARAM_CHECK
(
ret
==
0
,
free
(
param
);
return
NULL
,
"Failed to copy name %s"
,
name
);
param
->
workspace
=
workspace
;
param
->
nameLen
=
nameLen
;
param
->
paramValue
=
&
param
->
data
[
PARAM_ALIGN
(
nameLen
)
+
1
];
param
->
bufferLen
=
PARAM_VALUE_LEN_MAX
;
if
(
node
!=
NULL
&&
node
->
dataIndex
!=
0
)
{
param
->
dataIndex
=
node
->
dataIndex
;
ParamNode
*
entry
=
(
ParamNode
*
)
GetTrieNode
(
workspace
,
node
->
dataIndex
);
PARAM_CHECK
(
entry
!=
NULL
,
free
(
param
);
return
NULL
,
"Failed to get trie node %s"
,
name
);
uint32_t
length
=
param
->
bufferLen
;
param
->
dataCommitId
=
ReadCommitId
(
entry
);
ret
=
ReadParamValue_
(
entry
,
&
param
->
dataCommitId
,
param
->
paramValue
,
&
length
);
PARAM_CHECK
(
ret
==
0
,
free
(
param
);
return
NULL
,
"Failed to read parameter value %s"
,
name
);
}
else
{
param
->
dataIndex
=
0
;
ret
=
ParamStrCpy
(
param
->
paramValue
,
param
->
bufferLen
,
defValue
);
PARAM_CHECK
(
ret
==
0
,
free
(
param
);
return
NULL
,
"Failed to copy name %s"
,
name
);
}
param
->
spaceCommitId
=
ATOMIC_LOAD_EXPLICIT
(
&
workspace
->
area
->
commitId
,
memory_order_acquire
);
PARAM_LOGV
(
"CachedParameterCreate %u %u %lld
\n
"
,
param
->
dataIndex
,
param
->
dataCommitId
,
param
->
spaceCommitId
);
return
(
CachedHandle
)
param
;
}
static
const
char
*
CachedParameterCheck
(
CachedParameter
*
param
)
{
if
(
param
->
dataIndex
==
0
)
{
// no change, do not to find
long
long
spaceCommitId
=
ATOMIC_LOAD_EXPLICIT
(
&
param
->
workspace
->
area
->
commitId
,
memory_order_acquire
);
if
(
param
->
spaceCommitId
==
spaceCommitId
)
{
return
param
->
paramValue
;
}
param
->
spaceCommitId
=
spaceCommitId
;
ParamTrieNode
*
node
=
FindTrieNode
(
param
->
workspace
,
param
->
data
,
param
->
nameLen
,
NULL
);
if
(
node
!=
NULL
)
{
param
->
dataIndex
=
node
->
dataIndex
;
}
else
{
return
param
->
paramValue
;
}
}
ParamNode
*
entry
=
(
ParamNode
*
)
GetTrieNode
(
param
->
workspace
,
param
->
dataIndex
);
PARAM_CHECK
(
entry
!=
NULL
,
return
param
->
paramValue
,
"Failed to get trie node %s"
,
param
->
data
);
uint32_t
dataCommitId
=
ATOMIC_LOAD_EXPLICIT
(
&
entry
->
commitId
,
memory_order_acquire
);
dataCommitId
&=
PARAM_FLAGS_COMMITID
;
if
(
param
->
dataCommitId
==
dataCommitId
)
{
return
param
->
paramValue
;
}
uint32_t
length
=
param
->
bufferLen
;
param
->
dataCommitId
=
dataCommitId
;
int
ret
=
ReadParamValue_
(
entry
,
&
param
->
dataCommitId
,
param
->
paramValue
,
&
length
);
PARAM_CHECK
(
ret
==
0
,
return
NULL
,
"Failed to copy value %s"
,
param
->
data
);
PARAM_LOGI
(
"CachedParameterCheck %u"
,
param
->
dataCommitId
);
return
param
->
paramValue
;
}
const
char
*
CachedParameterGet
(
CachedHandle
handle
)
{
CachedParameter
*
param
=
(
CachedParameter
*
)
handle
;
PARAM_CHECK
(
param
!=
NULL
,
return
NULL
,
"Invalid handle %x"
,
handle
);
return
CachedParameterCheck
(
param
);
}
void
CachedParameterDestroy
(
CachedHandle
handle
)
{
if
(
handle
!=
NULL
)
{
free
(
handle
);
}
}
}
\ No newline at end of file
services/param/base/param_base.h
浏览文件 @
c485583a
...
@@ -32,6 +32,17 @@ INIT_LOCAL_API int ParamSprintf(char *buffer, size_t buffSize, const char *forma
...
@@ -32,6 +32,17 @@ INIT_LOCAL_API int ParamSprintf(char *buffer, size_t buffSize, const char *forma
INIT_LOCAL_API
int
ParamMemcpy
(
void
*
dest
,
size_t
destMax
,
const
void
*
src
,
size_t
count
);
INIT_LOCAL_API
int
ParamMemcpy
(
void
*
dest
,
size_t
destMax
,
const
void
*
src
,
size_t
count
);
INIT_LOCAL_API
int
ParamStrCpy
(
char
*
strDest
,
size_t
destMax
,
const
char
*
strSrc
);
INIT_LOCAL_API
int
ParamStrCpy
(
char
*
strDest
,
size_t
destMax
,
const
char
*
strSrc
);
typedef
struct
CachedParameter_
{
struct
WorkSpace_
*
workspace
;
long
long
spaceCommitId
;
uint32_t
dataCommitId
;
uint32_t
dataIndex
;
uint32_t
bufferLen
;
uint32_t
nameLen
;
char
*
paramValue
;
char
data
[
0
];
}
CachedParameter
;
#ifdef __cplusplus
#ifdef __cplusplus
#if __cplusplus
#if __cplusplus
}
}
...
...
services/param/base/param_comm.c
浏览文件 @
c485583a
...
@@ -484,31 +484,6 @@ INIT_LOCAL_API uint32_t ReadCommitId(ParamNode *entry)
...
@@ -484,31 +484,6 @@ INIT_LOCAL_API uint32_t ReadCommitId(ParamNode *entry)
return
commitId
&
PARAM_FLAGS_COMMITID
;
return
commitId
&
PARAM_FLAGS_COMMITID
;
}
}
INIT_LOCAL_API
int
ReadParamValue
(
ParamHandle
handle
,
char
*
value
,
uint32_t
*
length
)
{
ParamWorkSpace
*
paramSpace
=
GetParamWorkSpace
();
PARAM_CHECK
(
paramSpace
!=
NULL
,
return
-
1
,
"Invalid workspace"
);
PARAM_CHECK
(
length
!=
NULL
,
return
PARAM_CODE_INVALID_PARAM
,
"Invalid param"
);
ParamNode
*
entry
=
(
ParamNode
*
)
GetTrieNodeByHandle
(
handle
);
if
(
entry
==
NULL
)
{
return
PARAM_CODE_NOT_FOUND
;
}
if
(
value
==
NULL
)
{
*
length
=
entry
->
valueLength
+
1
;
return
0
;
}
PARAM_CHECK
(
*
length
>
entry
->
valueLength
,
return
PARAM_CODE_INVALID_PARAM
,
"Invalid value len %u %u"
,
*
length
,
entry
->
valueLength
);
uint32_t
commitId
=
ReadCommitId
(
entry
);
do
{
int
ret
=
ParamMemcpy
(
value
,
*
length
,
entry
->
data
+
entry
->
keyLength
+
1
,
entry
->
valueLength
);
PARAM_CHECK
(
ret
==
0
,
return
-
1
,
"Failed to copy value"
);
value
[
entry
->
valueLength
]
=
'\0'
;
*
length
=
entry
->
valueLength
;
}
while
(
commitId
!=
ReadCommitId
(
entry
));
return
0
;
}
INIT_LOCAL_API
int
ReadParamName
(
ParamHandle
handle
,
char
*
name
,
uint32_t
length
)
INIT_LOCAL_API
int
ReadParamName
(
ParamHandle
handle
,
char
*
name
,
uint32_t
length
)
{
{
ParamWorkSpace
*
paramSpace
=
GetParamWorkSpace
();
ParamWorkSpace
*
paramSpace
=
GetParamWorkSpace
();
...
...
services/param/include/param_manager.h
浏览文件 @
c485583a
...
@@ -118,7 +118,6 @@ INIT_LOCAL_API int WritePersistParam(const char *name, const char *value);
...
@@ -118,7 +118,6 @@ INIT_LOCAL_API int WritePersistParam(const char *name, const char *value);
INIT_LOCAL_API
uint32_t
ReadCommitId
(
ParamNode
*
entry
);
INIT_LOCAL_API
uint32_t
ReadCommitId
(
ParamNode
*
entry
);
INIT_LOCAL_API
int
ReadParamName
(
ParamHandle
handle
,
char
*
name
,
uint32_t
length
);
INIT_LOCAL_API
int
ReadParamName
(
ParamHandle
handle
,
char
*
name
,
uint32_t
length
);
INIT_LOCAL_API
int
ReadParamValue
(
ParamHandle
handle
,
char
*
value
,
uint32_t
*
length
);
INIT_LOCAL_API
int
CheckParameterSet
(
const
char
*
name
,
const
char
*
value
,
INIT_LOCAL_API
int
CheckParameterSet
(
const
char
*
name
,
const
char
*
value
,
const
ParamSecurityLabel
*
srcLabel
,
int
*
ctrlService
);
const
ParamSecurityLabel
*
srcLabel
,
int
*
ctrlService
);
INIT_LOCAL_API
ParamHandle
GetParamHandle
(
const
WorkSpace
*
workSpace
,
uint32_t
index
,
const
char
*
name
);
INIT_LOCAL_API
ParamHandle
GetParamHandle
(
const
WorkSpace
*
workSpace
,
uint32_t
index
,
const
char
*
name
);
...
...
test/moduletest/param_test_cmds.c
浏览文件 @
c485583a
...
@@ -333,6 +333,49 @@ static int32_t BShellParamCmdMemGet(BShellHandle shell, int32_t argc, char *argv
...
@@ -333,6 +333,49 @@ static int32_t BShellParamCmdMemGet(BShellHandle shell, int32_t argc, char *argv
return
0
;
return
0
;
}
}
#define MAX_TEST 10000
static
long
long
DiffLocalTime
(
struct
timespec
*
startTime
)
{
struct
timespec
endTime
;
clock_gettime
(
CLOCK_MONOTONIC
,
&
(
endTime
));
long
long
diff
=
(
long
long
)((
endTime
.
tv_sec
-
startTime
->
tv_sec
)
*
1000000
);
// 1000000 1000ms
if
(
endTime
.
tv_nsec
>
startTime
->
tv_nsec
)
{
diff
+=
(
endTime
.
tv_nsec
-
startTime
->
tv_nsec
)
/
1000
;
// 1000 1ms
}
else
{
diff
-=
(
startTime
->
tv_nsec
-
endTime
.
tv_nsec
)
/
1000
;
// 1000 1ms
}
return
diff
;
}
static
void
TestPerformance
(
const
char
*
testParamName
)
{
struct
timespec
startTime
;
CachedHandle
cacheHandle
=
CachedParameterCreate
(
testParamName
,
"true"
);
clock_gettime
(
CLOCK_MONOTONIC
,
&
(
startTime
));
const
char
*
value
=
NULL
;
for
(
int
i
=
0
;
i
<
MAX_TEST
;
++
i
)
{
value
=
CachedParameterGet
(
cacheHandle
);
}
CachedParameterDestroy
(
cacheHandle
);
printf
(
"CachedParameterGet time %lld us value %s
\n
"
,
DiffLocalTime
(
&
startTime
),
value
);
return
;
}
static
int32_t
BShellParamCmdPerformance
(
BShellHandle
shell
,
int32_t
argc
,
char
*
argv
[])
{
const
char
*
name
=
"test.performance.read"
;
TestPerformance
(
name
);
CachedHandle
cacheHandle
=
CachedParameterCreate
(
name
,
"true"
);
int
i
=
0
;
while
(
++
i
<
MAX_TEST
)
{
const
char
*
value
=
CachedParameterGet
(
cacheHandle
);
printf
(
"CachedParameterGet %s value %s
\n
"
,
name
,
value
);
usleep
(
100
);
}
CachedParameterDestroy
(
cacheHandle
);
return
0
;
}
int32_t
BShellCmdRegister
(
BShellHandle
shell
,
int
execMode
)
int32_t
BShellCmdRegister
(
BShellHandle
shell
,
int
execMode
)
{
{
if
(
execMode
==
0
)
{
if
(
execMode
==
0
)
{
...
@@ -353,6 +396,7 @@ int32_t BShellCmdRegister(BShellHandle shell, int execMode)
...
@@ -353,6 +396,7 @@ int32_t BShellCmdRegister(BShellHandle shell, int execMode)
{
"group"
,
BShellParamCmdGroupTest
,
"group test"
,
"group test [stage]"
,
"group test"
},
{
"group"
,
BShellParamCmdGroupTest
,
"group test"
,
"group test [stage]"
,
"group test"
},
{
"display"
,
BShellParamCmdUdidGet
,
"display udid"
,
"display udid"
,
"display udid"
},
{
"display"
,
BShellParamCmdUdidGet
,
"display udid"
,
"display udid"
,
"display udid"
},
{
"display"
,
BShellParamCmdMemGet
,
"display memory pid"
,
"display memory [pid]"
,
"display memory"
},
{
"display"
,
BShellParamCmdMemGet
,
"display memory pid"
,
"display memory [pid]"
,
"display memory"
},
{
"test"
,
BShellParamCmdPerformance
,
"test performance"
,
"test performance"
,
"test performance"
},
};
};
for
(
size_t
i
=
0
;
i
<
sizeof
(
infos
)
/
sizeof
(
infos
[
0
]);
i
++
)
{
for
(
size_t
i
=
0
;
i
<
sizeof
(
infos
)
/
sizeof
(
infos
[
0
]);
i
++
)
{
BShellEnvRegisterCmd
(
GetShellHandle
(),
&
infos
[
i
]);
BShellEnvRegisterCmd
(
GetShellHandle
(),
&
infos
[
i
]);
...
...
test/unittest/param/param_unittest.cpp
浏览文件 @
c485583a
...
@@ -438,4 +438,30 @@ HWTEST_F(ParamUnitTest, TestGetServiceCtlName, TestSize.Level0)
...
@@ -438,4 +438,30 @@ HWTEST_F(ParamUnitTest, TestGetServiceCtlName, TestSize.Level0)
free
(
serviceInfo
);
free
(
serviceInfo
);
}
}
}
}
HWTEST_F
(
ParamUnitTest
,
TestParamCache
,
TestSize
.
Level0
)
{
const
char
*
name
=
"test.write.1111111.222222"
;
CachedHandle
cacheHandle
=
CachedParameterCreate
(
name
,
"true"
);
EXPECT_NE
(
cacheHandle
,
nullptr
);
const
char
*
value
=
CachedParameterGet
(
cacheHandle
);
EXPECT_EQ
(
strcmp
(
value
,
"true"
),
0
);
uint32_t
dataIndex
=
0
;
int
ret
=
WriteParam
(
name
,
"false"
,
&
dataIndex
,
0
);
EXPECT_EQ
(
ret
,
0
);
value
=
CachedParameterGet
(
cacheHandle
);
EXPECT_EQ
(
strcmp
(
value
,
"false"
),
0
);
CachedParameterDestroy
(
cacheHandle
);
// cache 2, for parameter exist
CachedHandle
cacheHandle3
=
CachedParameterCreate
(
name
,
"true"
);
EXPECT_NE
(
cacheHandle3
,
nullptr
);
value
=
CachedParameterGet
(
cacheHandle3
);
EXPECT_EQ
(
strcmp
(
value
,
"false"
),
0
);
ret
=
WriteParam
(
name
,
"2222222"
,
&
dataIndex
,
0
);
EXPECT_EQ
(
ret
,
0
);
value
=
CachedParameterGet
(
cacheHandle3
);
EXPECT_EQ
(
strcmp
(
value
,
"2222222"
),
0
);
CachedParameterDestroy
(
cacheHandle3
);
}
}
}
\ No newline at end of file
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录