Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
wine-winehq
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
wine
wine-winehq
Commits
91049f5e
Commit
91049f5e
authored
Jul 13, 2012
by
Hans Leidekker
Committed by
Alexandre Julliard
Jul 13, 2012
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
wbemprox: Add support for creating new tables.
parent
4e6eb13c
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
303 additions
and
186 deletions
+303
-186
Makefile.in
dlls/wbemprox/Makefile.in
+1
-0
builtin.c
dlls/wbemprox/builtin.c
+6
-11
main.c
dlls/wbemprox/main.c
+1
-0
query.c
dlls/wbemprox/query.c
+1
-175
table.c
dlls/wbemprox/table.c
+280
-0
wbemprox_private.h
dlls/wbemprox/wbemprox_private.h
+14
-0
No files found.
dlls/wbemprox/Makefile.in
View file @
91049f5e
...
...
@@ -7,6 +7,7 @@ C_SRCS = \
main.c
\
query.c
\
services.c
\
table.c
\
wbemlocator.c
IDL_R_SRCS
=
wbemprox.idl
...
...
dlls/wbemprox/builtin.c
View file @
91049f5e
...
...
@@ -815,7 +815,7 @@ done:
if
(
factory
)
IDXGIFactory_Release
(
factory
);
}
static
struct
table
classtable
[]
=
static
struct
table
builtin_classes
[]
=
{
{
class_baseboardW
,
SIZEOF
(
col_baseboard
),
col_baseboard
,
SIZEOF
(
data_baseboard
),
(
BYTE
*
)
data_baseboard
},
{
class_biosW
,
SIZEOF
(
col_bios
),
col_bios
,
SIZEOF
(
data_bios
),
(
BYTE
*
)
data_bios
},
...
...
@@ -830,19 +830,14 @@ static struct table classtable[] =
{
class_videocontrollerW
,
SIZEOF
(
col_videocontroller
),
col_videocontroller
,
0
,
NULL
,
fill_videocontroller
}
};
struct
table
*
get_table
(
const
WCHAR
*
name
)
void
init_table_list
(
void
)
{
static
struct
list
tables
=
LIST_INIT
(
tables
);
UINT
i
;
struct
table
*
table
=
NULL
;
for
(
i
=
0
;
i
<
SIZEOF
(
classtable
);
i
++
)
for
(
i
=
0
;
i
<
SIZEOF
(
builtin_classes
);
i
++
)
{
if
(
!
strcmpiW
(
classtable
[
i
].
name
,
name
))
{
table
=
&
classtable
[
i
];
if
(
table
->
fill
&&
!
table
->
data
)
table
->
fill
(
table
);
break
;
}
list_add_tail
(
&
tables
,
&
builtin_classes
[
i
].
entry
);
}
return
table
;
table_list
=
&
tables
;
}
dlls/wbemprox/main.c
View file @
91049f5e
...
...
@@ -125,6 +125,7 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
case
DLL_PROCESS_ATTACH
:
instance
=
hinstDLL
;
DisableThreadLibraryCalls
(
hinstDLL
);
init_table_list
();
break
;
case
DLL_PROCESS_DETACH
:
break
;
...
...
dlls/wbemprox/query.c
View file @
91049f5e
...
...
@@ -30,156 +30,6 @@
WINE_DEFAULT_DEBUG_CHANNEL
(
wbemprox
);
static
HRESULT
get_column_index
(
const
struct
table
*
table
,
const
WCHAR
*
name
,
UINT
*
column
)
{
UINT
i
;
for
(
i
=
0
;
i
<
table
->
num_cols
;
i
++
)
{
if
(
!
strcmpiW
(
table
->
columns
[
i
].
name
,
name
))
{
*
column
=
i
;
return
S_OK
;
}
}
return
WBEM_E_INVALID_QUERY
;
}
static
UINT
get_column_size
(
const
struct
table
*
table
,
UINT
column
)
{
if
(
table
->
columns
[
column
].
type
&
CIM_FLAG_ARRAY
)
return
sizeof
(
void
*
);
switch
(
table
->
columns
[
column
].
type
&
COL_TYPE_MASK
)
{
case
CIM_SINT16
:
case
CIM_UINT16
:
return
sizeof
(
INT16
);
case
CIM_SINT32
:
case
CIM_UINT32
:
return
sizeof
(
INT32
);
case
CIM_SINT64
:
case
CIM_UINT64
:
return
sizeof
(
INT64
);
case
CIM_DATETIME
:
case
CIM_STRING
:
return
sizeof
(
WCHAR
*
);
default:
ERR
(
"unknown column type %u
\n
"
,
table
->
columns
[
column
].
type
&
COL_TYPE_MASK
);
break
;
}
return
sizeof
(
INT32
);
}
static
UINT
get_column_offset
(
const
struct
table
*
table
,
UINT
column
)
{
UINT
i
,
offset
=
0
;
for
(
i
=
0
;
i
<
column
;
i
++
)
offset
+=
get_column_size
(
table
,
i
);
return
offset
;
}
static
UINT
get_row_size
(
const
struct
table
*
table
)
{
return
get_column_offset
(
table
,
table
->
num_cols
-
1
)
+
get_column_size
(
table
,
table
->
num_cols
-
1
);
}
static
HRESULT
get_value
(
const
struct
table
*
table
,
UINT
row
,
UINT
column
,
LONGLONG
*
val
)
{
UINT
col_offset
,
row_size
;
const
BYTE
*
ptr
;
col_offset
=
get_column_offset
(
table
,
column
);
row_size
=
get_row_size
(
table
);
ptr
=
table
->
data
+
row
*
row_size
+
col_offset
;
if
(
table
->
columns
[
column
].
type
&
CIM_FLAG_ARRAY
)
{
*
val
=
(
LONGLONG
)(
INT_PTR
)
*
(
const
void
**
)
ptr
;
return
S_OK
;
}
switch
(
table
->
columns
[
column
].
type
&
COL_TYPE_MASK
)
{
case
CIM_DATETIME
:
case
CIM_STRING
:
*
val
=
(
LONGLONG
)(
INT_PTR
)
*
(
const
WCHAR
**
)
ptr
;
break
;
case
CIM_SINT16
:
*
val
=
*
(
const
INT16
*
)
ptr
;
break
;
case
CIM_UINT16
:
*
val
=
*
(
const
UINT16
*
)
ptr
;
break
;
case
CIM_SINT32
:
*
val
=
*
(
const
INT32
*
)
ptr
;
break
;
case
CIM_UINT32
:
*
val
=
*
(
const
UINT32
*
)
ptr
;
break
;
case
CIM_SINT64
:
*
val
=
*
(
const
INT64
*
)
ptr
;
break
;
case
CIM_UINT64
:
*
val
=
*
(
const
UINT64
*
)
ptr
;
break
;
default:
ERR
(
"invalid column type %u
\n
"
,
table
->
columns
[
column
].
type
&
COL_TYPE_MASK
);
*
val
=
0
;
break
;
}
return
S_OK
;
}
static
BSTR
get_value_bstr
(
const
struct
table
*
table
,
UINT
row
,
UINT
column
)
{
static
const
WCHAR
fmt_signedW
[]
=
{
'%'
,
'd'
,
0
};
static
const
WCHAR
fmt_unsignedW
[]
=
{
'%'
,
'u'
,
0
};
static
const
WCHAR
fmt_signed64W
[]
=
{
'%'
,
'I'
,
'6'
,
'4'
,
'd'
,
0
};
static
const
WCHAR
fmt_unsigned64W
[]
=
{
'%'
,
'I'
,
'6'
,
'4'
,
'u'
,
0
};
static
const
WCHAR
fmt_strW
[]
=
{
'\"'
,
'%'
,
's'
,
'\"'
,
0
};
LONGLONG
val
;
BSTR
ret
;
WCHAR
number
[
22
];
UINT
len
;
if
(
table
->
columns
[
column
].
type
&
CIM_FLAG_ARRAY
)
{
FIXME
(
"array to string conversion not handled
\n
"
);
return
NULL
;
}
if
(
get_value
(
table
,
row
,
column
,
&
val
)
!=
S_OK
)
return
NULL
;
switch
(
table
->
columns
[
column
].
type
&
COL_TYPE_MASK
)
{
case
CIM_DATETIME
:
case
CIM_STRING
:
len
=
strlenW
(
(
const
WCHAR
*
)(
INT_PTR
)
val
)
+
2
;
if
(
!
(
ret
=
SysAllocStringLen
(
NULL
,
len
)))
return
NULL
;
sprintfW
(
ret
,
fmt_strW
,
(
const
WCHAR
*
)(
INT_PTR
)
val
);
return
ret
;
case
CIM_SINT16
:
case
CIM_SINT32
:
sprintfW
(
number
,
fmt_signedW
,
val
);
return
SysAllocString
(
number
);
case
CIM_UINT16
:
case
CIM_UINT32
:
sprintfW
(
number
,
fmt_unsignedW
,
val
);
return
SysAllocString
(
number
);
case
CIM_SINT64
:
wsprintfW
(
number
,
fmt_signed64W
,
val
);
return
SysAllocString
(
number
);
case
CIM_UINT64
:
wsprintfW
(
number
,
fmt_unsigned64W
,
val
);
return
SysAllocString
(
number
);
default:
FIXME
(
"unhandled column type %u
\n
"
,
table
->
columns
[
column
].
type
&
COL_TYPE_MASK
);
break
;
}
return
NULL
;
}
HRESULT
create_view
(
const
struct
property
*
proplist
,
const
WCHAR
*
class
,
const
struct
expr
*
cond
,
struct
view
**
ret
)
{
...
...
@@ -195,33 +45,9 @@ HRESULT create_view( const struct property *proplist, const WCHAR *class,
return
S_OK
;
}
static
void
clear_table
(
struct
table
*
table
)
{
UINT
i
,
j
,
type
;
if
(
!
table
->
fill
||
!
table
->
data
)
return
;
for
(
i
=
0
;
i
<
table
->
num_rows
;
i
++
)
{
for
(
j
=
0
;
j
<
table
->
num_cols
;
j
++
)
{
if
(
!
(
table
->
columns
[
j
].
type
&
COL_FLAG_DYNAMIC
))
continue
;
type
=
table
->
columns
[
j
].
type
&
COL_TYPE_MASK
;
if
(
type
==
CIM_STRING
||
type
==
CIM_DATETIME
||
(
type
&
CIM_FLAG_ARRAY
))
{
void
*
ptr
;
if
(
get_value
(
table
,
i
,
j
,
(
LONGLONG
*
)
&
ptr
)
==
S_OK
)
heap_free
(
ptr
);
}
}
}
heap_free
(
table
->
data
);
table
->
data
=
NULL
;
}
void
destroy_view
(
struct
view
*
view
)
{
if
(
view
->
table
)
clear
_table
(
view
->
table
);
free
_table
(
view
->
table
);
heap_free
(
view
->
result
);
heap_free
(
view
);
}
...
...
dlls/wbemprox/table.c
0 → 100644
View file @
91049f5e
/*
* Copyright 2012 Hans Leidekker for CodeWeavers
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#define COBJMACROS
#include "config.h"
#include <stdarg.h>
#include "windef.h"
#include "winbase.h"
#include "wbemcli.h"
#include "wine/debug.h"
#include "wbemprox_private.h"
WINE_DEFAULT_DEBUG_CHANNEL
(
wbemprox
);
HRESULT
get_column_index
(
const
struct
table
*
table
,
const
WCHAR
*
name
,
UINT
*
column
)
{
UINT
i
;
for
(
i
=
0
;
i
<
table
->
num_cols
;
i
++
)
{
if
(
!
strcmpiW
(
table
->
columns
[
i
].
name
,
name
))
{
*
column
=
i
;
return
S_OK
;
}
}
return
WBEM_E_INVALID_QUERY
;
}
static
UINT
get_column_size
(
const
struct
table
*
table
,
UINT
column
)
{
if
(
table
->
columns
[
column
].
type
&
CIM_FLAG_ARRAY
)
return
sizeof
(
void
*
);
switch
(
table
->
columns
[
column
].
type
&
COL_TYPE_MASK
)
{
case
CIM_SINT16
:
case
CIM_UINT16
:
return
sizeof
(
INT16
);
case
CIM_SINT32
:
case
CIM_UINT32
:
return
sizeof
(
INT32
);
case
CIM_SINT64
:
case
CIM_UINT64
:
return
sizeof
(
INT64
);
case
CIM_DATETIME
:
case
CIM_STRING
:
return
sizeof
(
WCHAR
*
);
default:
ERR
(
"unknown column type %u
\n
"
,
table
->
columns
[
column
].
type
&
COL_TYPE_MASK
);
break
;
}
return
sizeof
(
INT32
);
}
static
UINT
get_column_offset
(
const
struct
table
*
table
,
UINT
column
)
{
UINT
i
,
offset
=
0
;
for
(
i
=
0
;
i
<
column
;
i
++
)
offset
+=
get_column_size
(
table
,
i
);
return
offset
;
}
static
UINT
get_row_size
(
const
struct
table
*
table
)
{
return
get_column_offset
(
table
,
table
->
num_cols
-
1
)
+
get_column_size
(
table
,
table
->
num_cols
-
1
);
}
HRESULT
get_value
(
const
struct
table
*
table
,
UINT
row
,
UINT
column
,
LONGLONG
*
val
)
{
UINT
col_offset
,
row_size
;
const
BYTE
*
ptr
;
col_offset
=
get_column_offset
(
table
,
column
);
row_size
=
get_row_size
(
table
);
ptr
=
table
->
data
+
row
*
row_size
+
col_offset
;
if
(
table
->
columns
[
column
].
type
&
CIM_FLAG_ARRAY
)
{
*
val
=
(
LONGLONG
)(
INT_PTR
)
*
(
const
void
**
)
ptr
;
return
S_OK
;
}
switch
(
table
->
columns
[
column
].
type
&
COL_TYPE_MASK
)
{
case
CIM_DATETIME
:
case
CIM_STRING
:
*
val
=
(
LONGLONG
)(
INT_PTR
)
*
(
const
WCHAR
**
)
ptr
;
break
;
case
CIM_SINT16
:
*
val
=
*
(
const
INT16
*
)
ptr
;
break
;
case
CIM_UINT16
:
*
val
=
*
(
const
UINT16
*
)
ptr
;
break
;
case
CIM_SINT32
:
*
val
=
*
(
const
INT32
*
)
ptr
;
break
;
case
CIM_UINT32
:
*
val
=
*
(
const
UINT32
*
)
ptr
;
break
;
case
CIM_SINT64
:
*
val
=
*
(
const
INT64
*
)
ptr
;
break
;
case
CIM_UINT64
:
*
val
=
*
(
const
UINT64
*
)
ptr
;
break
;
default:
ERR
(
"invalid column type %u
\n
"
,
table
->
columns
[
column
].
type
&
COL_TYPE_MASK
);
*
val
=
0
;
break
;
}
return
S_OK
;
}
BSTR
get_value_bstr
(
const
struct
table
*
table
,
UINT
row
,
UINT
column
)
{
static
const
WCHAR
fmt_signedW
[]
=
{
'%'
,
'd'
,
0
};
static
const
WCHAR
fmt_unsignedW
[]
=
{
'%'
,
'u'
,
0
};
static
const
WCHAR
fmt_signed64W
[]
=
{
'%'
,
'I'
,
'6'
,
'4'
,
'd'
,
0
};
static
const
WCHAR
fmt_unsigned64W
[]
=
{
'%'
,
'I'
,
'6'
,
'4'
,
'u'
,
0
};
static
const
WCHAR
fmt_strW
[]
=
{
'\"'
,
'%'
,
's'
,
'\"'
,
0
};
LONGLONG
val
;
BSTR
ret
;
WCHAR
number
[
22
];
UINT
len
;
if
(
table
->
columns
[
column
].
type
&
CIM_FLAG_ARRAY
)
{
FIXME
(
"array to string conversion not handled
\n
"
);
return
NULL
;
}
if
(
get_value
(
table
,
row
,
column
,
&
val
)
!=
S_OK
)
return
NULL
;
switch
(
table
->
columns
[
column
].
type
&
COL_TYPE_MASK
)
{
case
CIM_DATETIME
:
case
CIM_STRING
:
len
=
strlenW
(
(
const
WCHAR
*
)(
INT_PTR
)
val
)
+
2
;
if
(
!
(
ret
=
SysAllocStringLen
(
NULL
,
len
)))
return
NULL
;
sprintfW
(
ret
,
fmt_strW
,
(
const
WCHAR
*
)(
INT_PTR
)
val
);
return
ret
;
case
CIM_SINT16
:
case
CIM_SINT32
:
sprintfW
(
number
,
fmt_signedW
,
val
);
return
SysAllocString
(
number
);
case
CIM_UINT16
:
case
CIM_UINT32
:
sprintfW
(
number
,
fmt_unsignedW
,
val
);
return
SysAllocString
(
number
);
case
CIM_SINT64
:
wsprintfW
(
number
,
fmt_signed64W
,
val
);
return
SysAllocString
(
number
);
case
CIM_UINT64
:
wsprintfW
(
number
,
fmt_unsigned64W
,
val
);
return
SysAllocString
(
number
);
default:
FIXME
(
"unhandled column type %u
\n
"
,
table
->
columns
[
column
].
type
&
COL_TYPE_MASK
);
break
;
}
return
NULL
;
}
static
void
clear_table
(
struct
table
*
table
)
{
UINT
i
,
j
,
type
;
LONGLONG
val
;
if
(
!
table
->
data
)
return
;
for
(
i
=
0
;
i
<
table
->
num_rows
;
i
++
)
{
for
(
j
=
0
;
j
<
table
->
num_cols
;
j
++
)
{
if
(
!
(
table
->
columns
[
j
].
type
&
COL_FLAG_DYNAMIC
))
continue
;
type
=
table
->
columns
[
j
].
type
&
COL_TYPE_MASK
;
if
(
type
==
CIM_STRING
||
type
==
CIM_DATETIME
||
(
type
&
CIM_FLAG_ARRAY
))
{
if
(
get_value
(
table
,
i
,
j
,
&
val
)
==
S_OK
)
heap_free
(
(
void
*
)(
INT_PTR
)
val
);
}
}
}
table
->
num_rows
=
0
;
if
(
table
->
fill
)
{
heap_free
(
table
->
data
);
table
->
data
=
NULL
;
}
}
void
free_columns
(
struct
column
*
columns
,
UINT
num_cols
)
{
UINT
i
;
for
(
i
=
0
;
i
<
num_cols
;
i
++
)
{
heap_free
(
(
WCHAR
*
)
columns
[
i
].
name
);
}
heap_free
(
columns
);
}
void
free_table
(
struct
table
*
table
)
{
if
(
!
table
)
return
;
clear_table
(
table
);
if
(
table
->
flags
&
TABLE_FLAG_DYNAMIC
)
{
heap_free
(
(
WCHAR
*
)
table
->
name
);
free_columns
(
(
struct
column
*
)
table
->
columns
,
table
->
num_cols
);
heap_free
(
table
);
}
}
struct
table
*
get_table
(
const
WCHAR
*
name
)
{
struct
table
*
table
;
LIST_FOR_EACH_ENTRY
(
table
,
table_list
,
struct
table
,
entry
)
{
if
(
!
strcmpiW
(
table
->
name
,
name
))
{
if
(
table
->
fill
&&
!
table
->
data
)
table
->
fill
(
table
);
return
table
;
}
}
return
NULL
;
}
struct
table
*
create_table
(
const
WCHAR
*
name
,
UINT
num_cols
,
const
struct
column
*
columns
,
UINT
num_rows
,
BYTE
*
data
,
void
(
*
fill
)(
struct
table
*
)
)
{
struct
table
*
table
;
if
(
!
(
table
=
heap_alloc
(
sizeof
(
*
table
)
)))
return
NULL
;
table
->
name
=
name
;
table
->
num_cols
=
num_cols
;
table
->
columns
=
columns
;
table
->
num_rows
=
num_rows
;
table
->
data
=
data
;
table
->
fill
=
fill
;
table
->
flags
=
TABLE_FLAG_DYNAMIC
;
return
table
;
}
BOOL
add_table
(
struct
table
*
table
)
{
struct
table
*
iter
;
LIST_FOR_EACH_ENTRY
(
iter
,
table_list
,
struct
table
,
entry
)
{
if
(
!
strcmpiW
(
iter
->
name
,
table
->
name
))
{
TRACE
(
"table %s already exists
\n
"
,
debugstr_w
(
table
->
name
));
return
FALSE
;
}
}
list_add_tail
(
table_list
,
&
table
->
entry
);
return
TRUE
;
}
dlls/wbemprox/wbemprox_private.h
View file @
91049f5e
...
...
@@ -20,6 +20,7 @@
#include "wine/unicode.h"
IClientSecurity
client_security
;
struct
list
*
table_list
;
#define SIZEOF(array) (sizeof(array)/sizeof((array)[0]))
...
...
@@ -37,6 +38,8 @@ struct column
VARTYPE
vartype
;
/* 0 for default mapping */
};
#define TABLE_FLAG_DYNAMIC 0x00000001
struct
table
{
const
WCHAR
*
name
;
...
...
@@ -45,6 +48,8 @@ struct table
UINT
num_rows
;
BYTE
*
data
;
void
(
*
fill
)(
struct
table
*
);
UINT
flags
;
struct
list
entry
;
};
struct
property
...
...
@@ -122,7 +127,16 @@ HRESULT parse_query( const WCHAR *, struct view **, struct list * ) DECLSPEC_HID
HRESULT
create_view
(
const
struct
property
*
,
const
WCHAR
*
,
const
struct
expr
*
,
struct
view
**
)
DECLSPEC_HIDDEN
;
void
destroy_view
(
struct
view
*
)
DECLSPEC_HIDDEN
;
void
init_table_list
(
void
)
DECLSPEC_HIDDEN
;
struct
table
*
get_table
(
const
WCHAR
*
)
DECLSPEC_HIDDEN
;
struct
table
*
create_table
(
const
WCHAR
*
,
UINT
,
const
struct
column
*
,
UINT
,
BYTE
*
,
void
(
*
)(
struct
table
*
))
DECLSPEC_HIDDEN
;
BOOL
add_table
(
struct
table
*
)
DECLSPEC_HIDDEN
;
void
free_columns
(
struct
column
*
,
UINT
)
DECLSPEC_HIDDEN
;
void
free_table
(
struct
table
*
)
DECLSPEC_HIDDEN
;
HRESULT
get_column_index
(
const
struct
table
*
,
const
WCHAR
*
,
UINT
*
)
DECLSPEC_HIDDEN
;
HRESULT
get_value
(
const
struct
table
*
,
UINT
,
UINT
,
LONGLONG
*
)
DECLSPEC_HIDDEN
;
BSTR
get_value_bstr
(
const
struct
table
*
,
UINT
,
UINT
)
DECLSPEC_HIDDEN
;
HRESULT
get_propval
(
const
struct
view
*
,
UINT
,
const
WCHAR
*
,
VARIANT
*
,
CIMTYPE
*
,
LONG
*
)
DECLSPEC_HIDDEN
;
HRESULT
get_properties
(
const
struct
view
*
,
SAFEARRAY
**
)
DECLSPEC_HIDDEN
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment