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
755f6a0a
Commit
755f6a0a
authored
Jan 12, 2024
by
Hans Leidekker
Committed by
Alexandre Julliard
Jan 19, 2024
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
sxs: Use wide character string literals.
parent
4f8939e1
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
40 additions
and
61 deletions
+40
-61
cache.c
dlls/sxs/cache.c
+28
-44
name.c
dlls/sxs/name.c
+12
-17
No files found.
dlls/sxs/cache.c
View file @
755f6a0a
...
...
@@ -35,12 +35,7 @@
WINE_DEFAULT_DEBUG_CHANNEL
(
sxs
);
static
const
WCHAR
cache_mutex_nameW
[]
=
{
'_'
,
'_'
,
'W'
,
'I'
,
'N'
,
'E'
,
'_'
,
'S'
,
'X'
,
'S'
,
'_'
,
'C'
,
'A'
,
'C'
,
'H'
,
'E'
,
'_'
,
'M'
,
'U'
,
'T'
,
'E'
,
'X'
,
'_'
,
'_'
,
0
};
static
const
WCHAR
win32W
[]
=
{
'w'
,
'i'
,
'n'
,
'3'
,
'2'
,
0
};
static
const
WCHAR
win32_policyW
[]
=
{
'w'
,
'i'
,
'n'
,
'3'
,
'2'
,
'-'
,
'p'
,
'o'
,
'l'
,
'i'
,
'c'
,
'y'
,
0
};
static
const
WCHAR
backslashW
[]
=
{
'\\'
,
0
};
static
const
WCHAR
cache_mutex_nameW
[]
=
L"__WINE_SXS_CACHE_MUTEX__"
;
struct
cache
{
...
...
@@ -96,7 +91,7 @@ static ULONG WINAPI cache_Release( IAssemblyCache *iface )
static
unsigned
int
build_sxs_path
(
WCHAR
*
path
)
{
static
const
WCHAR
winsxsW
[]
=
{
'\\'
,
'w'
,
'i'
,
'n'
,
's'
,
'x'
,
's'
,
'\\'
,
0
}
;
static
const
WCHAR
winsxsW
[]
=
L"
\\
winsxs
\\
"
;
unsigned
int
len
=
GetWindowsDirectoryW
(
path
,
MAX_PATH
);
memcpy
(
path
+
len
,
winsxsW
,
sizeof
(
winsxsW
)
);
...
...
@@ -106,8 +101,7 @@ static unsigned int build_sxs_path( WCHAR *path )
static
WCHAR
*
build_assembly_name
(
const
WCHAR
*
arch
,
const
WCHAR
*
name
,
const
WCHAR
*
token
,
const
WCHAR
*
version
,
unsigned
int
*
len
)
{
static
const
WCHAR
fmtW
[]
=
{
'%'
,
's'
,
'_'
,
'%'
,
's'
,
'_'
,
'%'
,
's'
,
'_'
,
'%'
,
's'
,
'_'
,
'n'
,
'o'
,
'n'
,
'e'
,
'_'
,
'd'
,
'e'
,
'a'
,
'd'
,
'b'
,
'e'
,
'e'
,
'f'
,
0
};
static
const
WCHAR
fmtW
[]
=
L"%s_%s_%s_%s_none_deadbeef"
;
unsigned
int
buflen
=
ARRAY_SIZE
(
fmtW
);
WCHAR
*
ret
;
...
...
@@ -143,8 +137,7 @@ static WCHAR *build_dll_path( const WCHAR *arch, const WCHAR *name, const WCHAR
static
WCHAR
*
build_policy_name
(
const
WCHAR
*
arch
,
const
WCHAR
*
name
,
const
WCHAR
*
token
,
unsigned
int
*
len
)
{
static
const
WCHAR
fmtW
[]
=
{
'%'
,
's'
,
'_'
,
'%'
,
's'
,
'_'
,
'%'
,
's'
,
'_'
,
'n'
,
'o'
,
'n'
,
'e'
,
'_'
,
'd'
,
'e'
,
'a'
,
'd'
,
'b'
,
'e'
,
'e'
,
'f'
,
0
};
static
const
WCHAR
fmtW
[]
=
L"%s_%s_%s_none_deadbeef"
;
unsigned
int
buflen
=
ARRAY_SIZE
(
fmtW
);
WCHAR
*
ret
;
...
...
@@ -159,8 +152,7 @@ static WCHAR *build_policy_name( const WCHAR *arch, const WCHAR *name, const WCH
static
WCHAR
*
build_policy_path
(
const
WCHAR
*
arch
,
const
WCHAR
*
name
,
const
WCHAR
*
token
,
const
WCHAR
*
version
)
{
static
const
WCHAR
fmtW
[]
=
{
'%'
,
's'
,
'p'
,
'o'
,
'l'
,
'i'
,
'c'
,
'i'
,
'e'
,
's'
,
'\\'
,
'%'
,
's'
,
'\\'
,
'%'
,
's'
,
'.'
,
'p'
,
'o'
,
'l'
,
'i'
,
'c'
,
'y'
,
0
};
static
const
WCHAR
fmtW
[]
=
L"%spolicies
\\
%s
\\
%s.policy"
;
WCHAR
*
path
=
NULL
,
*
ret
,
sxsdir
[
MAX_PATH
];
unsigned
int
len
;
...
...
@@ -229,8 +221,8 @@ static HRESULT WINAPI cache_QueryAssemblyInfo(
}
cache_lock
(
cache
);
if
(
!
wcscmp
(
type
,
win32W
))
path
=
build_dll_path
(
arch
,
name
,
token
,
version
);
else
if
(
!
wcscmp
(
type
,
win32_policyW
))
path
=
build_policy_path
(
arch
,
name
,
token
,
version
);
if
(
!
wcscmp
(
type
,
L"win32"
))
path
=
build_dll_path
(
arch
,
name
,
token
,
version
);
else
if
(
!
wcscmp
(
type
,
L"win32-policy"
))
path
=
build_policy_path
(
arch
,
name
,
token
,
version
);
else
{
hr
=
HRESULT_FROM_WIN32
(
ERROR_SXS_INVALID_IDENTITY_ATTRIBUTE_VALUE
);
...
...
@@ -346,8 +338,6 @@ static void free_assembly( struct assembly *assembly )
static
HRESULT
parse_files
(
IXMLDOMDocument
*
doc
,
struct
assembly
*
assembly
)
{
static
const
WCHAR
fileW
[]
=
{
'f'
,
'i'
,
'l'
,
'e'
,
0
};
static
const
WCHAR
nameW
[]
=
{
'n'
,
'a'
,
'm'
,
'e'
,
0
};
IXMLDOMNamedNodeMap
*
attrs
;
IXMLDOMNodeList
*
list
;
IXMLDOMNode
*
node
;
...
...
@@ -356,7 +346,7 @@ static HRESULT parse_files( IXMLDOMDocument *doc, struct assembly *assembly )
HRESULT
hr
;
LONG
len
;
str
=
SysAllocString
(
fileW
);
str
=
SysAllocString
(
L"file"
);
hr
=
IXMLDOMDocument_getElementsByTagName
(
doc
,
str
,
&
list
);
SysFreeString
(
str
);
if
(
hr
!=
S_OK
)
return
hr
;
...
...
@@ -393,7 +383,7 @@ static HRESULT parse_files( IXMLDOMDocument *doc, struct assembly *assembly )
goto
done
;
}
f
->
name
=
get_attribute_value
(
attrs
,
nameW
);
f
->
name
=
get_attribute_value
(
attrs
,
L"name"
);
IXMLDOMNamedNodeMap_Release
(
attrs
);
if
(
!
f
->
name
)
{
...
...
@@ -417,12 +407,6 @@ done:
static
HRESULT
parse_assembly
(
IXMLDOMDocument
*
doc
,
struct
assembly
**
assembly
)
{
static
const
WCHAR
identityW
[]
=
{
'a'
,
's'
,
's'
,
'e'
,
'm'
,
'b'
,
'l'
,
'y'
,
'I'
,
'd'
,
'e'
,
'n'
,
't'
,
'i'
,
't'
,
'y'
,
0
};
static
const
WCHAR
typeW
[]
=
{
't'
,
'y'
,
'p'
,
'e'
,
0
};
static
const
WCHAR
nameW
[]
=
{
'n'
,
'a'
,
'm'
,
'e'
,
0
};
static
const
WCHAR
versionW
[]
=
{
'v'
,
'e'
,
'r'
,
's'
,
'i'
,
'o'
,
'n'
,
0
};
static
const
WCHAR
architectureW
[]
=
{
'p'
,
'r'
,
'o'
,
'c'
,
'e'
,
's'
,
's'
,
'o'
,
'r'
,
'A'
,
'r'
,
'c'
,
'h'
,
'i'
,
't'
,
'e'
,
'c'
,
't'
,
'u'
,
'r'
,
'e'
,
0
};
static
const
WCHAR
tokenW
[]
=
{
'p'
,
'u'
,
'b'
,
'l'
,
'i'
,
'c'
,
'K'
,
'e'
,
'y'
,
'T'
,
'o'
,
'k'
,
'e'
,
'n'
,
0
};
IXMLDOMNodeList
*
list
=
NULL
;
IXMLDOMNode
*
node
=
NULL
;
IXMLDOMNamedNodeMap
*
attrs
=
NULL
;
...
...
@@ -431,7 +415,7 @@ static HRESULT parse_assembly( IXMLDOMDocument *doc, struct assembly **assembly
HRESULT
hr
;
LONG
len
;
str
=
SysAllocString
(
identityW
);
str
=
SysAllocString
(
L"assemblyIdentity"
);
hr
=
IXMLDOMDocument_getElementsByTagName
(
doc
,
str
,
&
list
);
SysFreeString
(
str
);
if
(
hr
!=
S_OK
)
goto
done
;
...
...
@@ -460,20 +444,20 @@ static HRESULT parse_assembly( IXMLDOMDocument *doc, struct assembly **assembly
hr
=
IXMLDOMNode_get_attributes
(
node
,
&
attrs
);
if
(
hr
!=
S_OK
)
goto
done
;
a
->
type
=
get_attribute_value
(
attrs
,
typeW
);
a
->
name
=
get_attribute_value
(
attrs
,
nameW
);
a
->
version
=
get_attribute_value
(
attrs
,
versionW
);
a
->
arch
=
get_attribute_value
(
attrs
,
architectureW
);
a
->
token
=
get_attribute_value
(
attrs
,
tokenW
);
a
->
type
=
get_attribute_value
(
attrs
,
L"type"
);
a
->
name
=
get_attribute_value
(
attrs
,
L"name"
);
a
->
version
=
get_attribute_value
(
attrs
,
L"version"
);
a
->
arch
=
get_attribute_value
(
attrs
,
L"processorArchitecture"
);
a
->
token
=
get_attribute_value
(
attrs
,
L"publicKeyToken"
);
if
(
!
a
->
type
||
(
wcscmp
(
a
->
type
,
win32W
)
&&
wcscmp
(
a
->
type
,
win32_policyW
))
||
if
(
!
a
->
type
||
(
wcscmp
(
a
->
type
,
L"win32"
)
&&
wcscmp
(
a
->
type
,
L"win32-policy"
))
||
!
a
->
name
||
!
a
->
version
||
!
a
->
arch
||
!
a
->
token
)
{
WARN
(
"invalid win32 assembly
\n
"
);
hr
=
ERROR_SXS_MANIFEST_FORMAT_ERROR
;
goto
done
;
}
if
(
!
wcscmp
(
a
->
type
,
win32W
))
hr
=
parse_files
(
doc
,
a
);
if
(
!
wcscmp
(
a
->
type
,
L"win32"
))
hr
=
parse_files
(
doc
,
a
);
done:
if
(
attrs
)
IXMLDOMNamedNodeMap_Release
(
attrs
);
...
...
@@ -487,8 +471,8 @@ done:
static
WCHAR
*
build_policy_filename
(
const
WCHAR
*
arch
,
const
WCHAR
*
name
,
const
WCHAR
*
token
,
const
WCHAR
*
version
)
{
static
const
WCHAR
policiesW
[]
=
{
'p'
,
'o'
,
'l'
,
'i'
,
'c'
,
'i'
,
'e'
,
's'
,
'\\'
,
0
}
;
static
const
WCHAR
suffixW
[]
=
{
'.'
,
'p'
,
'o'
,
'l'
,
'i'
,
'c'
,
'y'
,
0
}
;
static
const
WCHAR
policiesW
[]
=
L"policies
\\
"
;
static
const
WCHAR
suffixW
[]
=
L".policy"
;
WCHAR
sxsdir
[
MAX_PATH
],
*
ret
,
*
fullname
;
unsigned
int
len
;
...
...
@@ -507,7 +491,7 @@ static WCHAR *build_policy_filename( const WCHAR *arch, const WCHAR *name, const
CreateDirectoryW
(
ret
,
NULL
);
lstrcatW
(
ret
,
name
);
CreateDirectoryW
(
ret
,
NULL
);
lstrcatW
(
ret
,
backslashW
);
lstrcatW
(
ret
,
L"
\\
"
);
lstrcatW
(
ret
,
version
);
lstrcatW
(
ret
,
suffixW
);
...
...
@@ -558,8 +542,8 @@ static WCHAR *build_source_filename( const WCHAR *manifest, struct file *file )
static
WCHAR
*
build_manifest_filename
(
const
WCHAR
*
arch
,
const
WCHAR
*
name
,
const
WCHAR
*
token
,
const
WCHAR
*
version
)
{
static
const
WCHAR
manifestsW
[]
=
{
'm'
,
'a'
,
'n'
,
'i'
,
'f'
,
'e'
,
's'
,
't'
,
's'
,
'\\'
,
0
}
;
static
const
WCHAR
suffixW
[]
=
{
'.'
,
'm'
,
'a'
,
'n'
,
'i'
,
'f'
,
'e'
,
's'
,
't'
,
0
}
;
static
const
WCHAR
manifestsW
[]
=
L"manifests
\\
"
;
static
const
WCHAR
suffixW
[]
=
L".manifest"
;
WCHAR
sxsdir
[
MAX_PATH
],
*
ret
,
*
fullname
;
unsigned
int
len
;
...
...
@@ -642,7 +626,7 @@ static HRESULT install_assembly( const WCHAR *manifest, struct assembly *assembl
lstrcatW
(
dst
,
name
);
CreateDirectoryW
(
dst
,
NULL
);
lstrcatW
(
dst
,
backslashW
);
lstrcatW
(
dst
,
L"
\\
"
);
lstrcatW
(
dst
,
file
->
name
);
for
(
p
=
dst
;
*
p
;
p
++
)
*
p
=
towlower
(
*
p
);
...
...
@@ -688,7 +672,7 @@ static HRESULT WINAPI cache_InstallAssembly(
/* FIXME: verify name attributes */
if
(
!
wcscmp
(
assembly
->
type
,
win32_policyW
))
if
(
!
wcscmp
(
assembly
->
type
,
L"win32-policy"
))
hr
=
install_policy
(
path
,
assembly
);
else
hr
=
install_assembly
(
path
,
assembly
);
...
...
@@ -721,7 +705,7 @@ static HRESULT uninstall_assembly( struct assembly *assembly )
len
=
len_sxsdir
+
len_name
+
1
+
lstrlenW
(
file
->
name
);
if
(
!
(
filename
=
malloc
(
(
len
+
1
)
*
sizeof
(
WCHAR
)
)))
goto
done
;
lstrcpyW
(
filename
,
dirname
);
lstrcatW
(
filename
,
backslashW
);
lstrcatW
(
filename
,
L"
\\
"
);
lstrcatW
(
filename
,
file
->
name
);
if
(
!
DeleteFileW
(
filename
))
WARN
(
"failed to delete file %lu
\n
"
,
GetLastError
()
);
...
...
@@ -775,8 +759,8 @@ static HRESULT WINAPI cache_UninstallAssembly(
hr
=
E_INVALIDARG
;
goto
done
;
}
if
(
!
wcscmp
(
type
,
win32W
))
path
=
build_manifest_filename
(
arch
,
name
,
token
,
version
);
else
if
(
!
wcscmp
(
type
,
win32_policyW
))
path
=
build_policy_filename
(
arch
,
name
,
token
,
version
);
if
(
!
wcscmp
(
type
,
L"win32"
))
path
=
build_manifest_filename
(
arch
,
name
,
token
,
version
);
else
if
(
!
wcscmp
(
type
,
L"win32-policy"
))
path
=
build_policy_filename
(
arch
,
name
,
token
,
version
);
else
{
hr
=
E_INVALIDARG
;
...
...
@@ -796,7 +780,7 @@ static HRESULT WINAPI cache_UninstallAssembly(
*
p
=
0
;
RemoveDirectoryW
(
path
);
}
if
(
!
wcscmp
(
assembly
->
type
,
win32W
))
hr
=
uninstall_assembly
(
assembly
);
if
(
!
wcscmp
(
assembly
->
type
,
L"win32"
))
hr
=
uninstall_assembly
(
assembly
);
done:
if
(
name_obj
)
IAssemblyName_Release
(
name_obj
);
...
...
dlls/sxs/name.c
View file @
755f6a0a
...
...
@@ -43,11 +43,6 @@ struct name
WCHAR
*
version
;
};
static
const
WCHAR
archW
[]
=
{
'p'
,
'r'
,
'o'
,
'c'
,
'e'
,
's'
,
's'
,
'o'
,
'r'
,
'A'
,
'r'
,
'c'
,
'h'
,
'i'
,
't'
,
'e'
,
'c'
,
't'
,
'u'
,
'r'
,
'e'
,
0
};
static
const
WCHAR
tokenW
[]
=
{
'p'
,
'u'
,
'b'
,
'l'
,
'i'
,
'c'
,
'K'
,
'e'
,
'y'
,
'T'
,
'o'
,
'k'
,
'e'
,
'n'
,
0
};
static
const
WCHAR
typeW
[]
=
{
't'
,
'y'
,
'p'
,
'e'
,
0
};
static
const
WCHAR
versionW
[]
=
{
'v'
,
'e'
,
'r'
,
's'
,
'i'
,
'o'
,
'n'
,
0
};
static
inline
struct
name
*
impl_from_IAssemblyName
(
IAssemblyName
*
iface
)
{
return
CONTAINING_RECORD
(
iface
,
struct
name
,
IAssemblyName_iface
);
...
...
@@ -140,10 +135,10 @@ static HRESULT WINAPI name_GetDisplayName(
if
(
!
buflen
||
flags
)
return
E_INVALIDARG
;
len
=
lstrlenW
(
name
->
name
)
+
1
;
if
(
name
->
arch
)
len
+=
lstrlenW
(
archW
)
+
lstrlenW
(
name
->
arch
)
+
4
;
if
(
name
->
token
)
len
+=
lstrlenW
(
tokenW
)
+
lstrlenW
(
name
->
token
)
+
4
;
if
(
name
->
type
)
len
+=
lstrlenW
(
typeW
)
+
lstrlenW
(
name
->
type
)
+
4
;
if
(
name
->
version
)
len
+=
lstrlenW
(
versionW
)
+
lstrlenW
(
name
->
version
)
+
4
;
if
(
name
->
arch
)
len
+=
lstrlenW
(
L"processorArchitecture"
)
+
lstrlenW
(
name
->
arch
)
+
4
;
if
(
name
->
token
)
len
+=
lstrlenW
(
L"publicKeyToken"
)
+
lstrlenW
(
name
->
token
)
+
4
;
if
(
name
->
type
)
len
+=
lstrlenW
(
L"type"
)
+
lstrlenW
(
name
->
type
)
+
4
;
if
(
name
->
version
)
len
+=
lstrlenW
(
L"version"
)
+
lstrlenW
(
name
->
version
)
+
4
;
if
(
len
>
*
buflen
)
{
*
buflen
=
len
;
...
...
@@ -151,10 +146,10 @@ static HRESULT WINAPI name_GetDisplayName(
}
lstrcpyW
(
buffer
,
name
->
name
);
len
=
lstrlenW
(
buffer
);
if
(
name
->
arch
)
len
+=
swprintf
(
buffer
+
len
,
*
buflen
-
len
,
fmtW
,
archW
,
name
->
arch
);
if
(
name
->
token
)
len
+=
swprintf
(
buffer
+
len
,
*
buflen
-
len
,
fmtW
,
tokenW
,
name
->
token
);
if
(
name
->
type
)
len
+=
swprintf
(
buffer
+
len
,
*
buflen
-
len
,
fmtW
,
typeW
,
name
->
type
);
if
(
name
->
version
)
len
+=
swprintf
(
buffer
+
len
,
*
buflen
-
len
,
fmtW
,
versionW
,
name
->
version
);
if
(
name
->
arch
)
len
+=
swprintf
(
buffer
+
len
,
*
buflen
-
len
,
fmtW
,
L"processorArchitecture"
,
name
->
arch
);
if
(
name
->
token
)
len
+=
swprintf
(
buffer
+
len
,
*
buflen
-
len
,
fmtW
,
L"publicKeyToken"
,
name
->
token
);
if
(
name
->
type
)
len
+=
swprintf
(
buffer
+
len
,
*
buflen
-
len
,
fmtW
,
L"type"
,
name
->
type
);
if
(
name
->
version
)
len
+=
swprintf
(
buffer
+
len
,
*
buflen
-
len
,
fmtW
,
L"version"
,
name
->
version
);
return
S_OK
;
}
...
...
@@ -325,25 +320,25 @@ static HRESULT parse_displayname( struct name *name, const WCHAR *displayname )
while
(
*
q
&&
*
q
!=
'='
)
q
++
;
if
(
!*
q
)
return
E_INVALIDARG
;
len
=
q
-
p
;
if
(
len
==
ARRAY_SIZE
(
archW
)
-
1
&&
!
memcmp
(
p
,
archW
,
len
*
sizeof
(
WCHAR
)
))
if
(
len
==
ARRAY_SIZE
(
L"processorArchitecture"
)
-
1
&&
!
memcmp
(
p
,
L"processorArchitecture"
,
len
*
sizeof
(
WCHAR
)
))
{
p
=
++
q
;
if
(
!
(
name
->
arch
=
parse_value
(
p
,
&
len
)))
return
E_INVALIDARG
;
q
+=
len
;
}
else
if
(
len
==
ARRAY_SIZE
(
tokenW
)
-
1
&&
!
memcmp
(
p
,
tokenW
,
len
*
sizeof
(
WCHAR
)
))
else
if
(
len
==
ARRAY_SIZE
(
L"publicKeyToken"
)
-
1
&&
!
memcmp
(
p
,
L"publicKeyToken"
,
len
*
sizeof
(
WCHAR
)
))
{
p
=
++
q
;
if
(
!
(
name
->
token
=
parse_value
(
p
,
&
len
)))
return
E_INVALIDARG
;
q
+=
len
;
}
else
if
(
len
==
ARRAY_SIZE
(
typeW
)
-
1
&&
!
memcmp
(
p
,
typeW
,
len
*
sizeof
(
WCHAR
)
))
else
if
(
len
==
ARRAY_SIZE
(
L"type"
)
-
1
&&
!
memcmp
(
p
,
L"type"
,
len
*
sizeof
(
WCHAR
)
))
{
p
=
++
q
;
if
(
!
(
name
->
type
=
parse_value
(
p
,
&
len
)))
return
E_INVALIDARG
;
q
+=
len
;
}
else
if
(
len
==
ARRAY_SIZE
(
versionW
)
-
1
&&
!
memcmp
(
p
,
versionW
,
len
*
sizeof
(
WCHAR
)
))
else
if
(
len
==
ARRAY_SIZE
(
L"version"
)
-
1
&&
!
memcmp
(
p
,
L"version"
,
len
*
sizeof
(
WCHAR
)
))
{
p
=
++
q
;
if
(
!
(
name
->
version
=
parse_value
(
p
,
&
len
)))
return
E_INVALIDARG
;
...
...
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