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
9ca159b6
Commit
9ca159b6
authored
Aug 23, 2003
by
Alexandre Julliard
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Moved 16-bit builtin module handling to dlls/kernel/ne_module.c.
parent
1c6a20f3
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
237 additions
and
278 deletions
+237
-278
ne_module.c
dlls/kernel/ne_module.c
+233
-2
Makefile.in
dlls/ntdll/Makefile.in
+0
-1
builtin.c
if1632/builtin.c
+0
-256
module.h
include/module.h
+0
-4
loadorder.c
loader/loadorder.c
+4
-15
No files found.
dlls/kernel/ne_module.c
View file @
9ca159b6
...
...
@@ -38,6 +38,7 @@
#include "wine/library.h"
#include "module.h"
#include "toolhelp.h"
#include "global.h"
#include "file.h"
#include "task.h"
#include "snoop.h"
...
...
@@ -75,6 +76,23 @@ struct ne_segment_table_entry_s
static
NE_MODULE
*
pCachedModule
=
0
;
/* Module cached by NE_OpenFile */
typedef
struct
{
void
*
module_start
;
/* 32-bit address of the module data */
int
module_size
;
/* Size of the module data */
void
*
code_start
;
/* 32-bit address of DLL code */
void
*
data_start
;
/* 32-bit address of DLL data */
const
char
*
owner
;
/* 32-bit dll that contains this dll */
const
void
*
rsrc
;
/* resources data */
}
BUILTIN16_DESCRIPTOR
;
/* Table of all built-in DLLs */
#define MAX_DLLS 50
static
const
BUILTIN16_DESCRIPTOR
*
builtin_dlls
[
MAX_DLLS
];
static
HINSTANCE16
NE_LoadModule
(
LPCSTR
name
,
BOOL
lib_only
);
static
BOOL16
NE_FreeModule
(
HMODULE16
hModule
,
BOOL
call_wep
);
...
...
@@ -92,6 +110,106 @@ static WINE_EXCEPTION_FILTER(page_fault)
}
/* patch all the flat cs references of the code segment if necessary */
inline
static
void
patch_code_segment
(
void
*
code_segment
)
{
#ifdef __i386__
CALLFROM16
*
call
=
code_segment
;
if
(
call
->
flatcs
==
wine_get_cs
())
return
;
/* nothing to patch */
while
(
call
->
pushl
==
0x68
)
{
call
->
flatcs
=
wine_get_cs
();
call
++
;
}
#endif
}
/***********************************************************************
* find_dll_descr
*
* Find a descriptor in the list
*/
static
const
BUILTIN16_DESCRIPTOR
*
find_dll_descr
(
const
char
*
dllname
)
{
int
i
;
for
(
i
=
0
;
i
<
MAX_DLLS
;
i
++
)
{
const
BUILTIN16_DESCRIPTOR
*
descr
=
builtin_dlls
[
i
];
if
(
descr
)
{
NE_MODULE
*
pModule
=
(
NE_MODULE
*
)
descr
->
module_start
;
OFSTRUCT
*
pOfs
=
(
OFSTRUCT
*
)((
LPBYTE
)
pModule
+
pModule
->
fileinfo
);
BYTE
*
name_table
=
(
BYTE
*
)
pModule
+
pModule
->
name_table
;
/* check the dll file name */
if
(
!
FILE_strcasecmp
(
pOfs
->
szPathName
,
dllname
))
return
descr
;
/* check the dll module name (without extension) */
if
(
!
FILE_strncasecmp
(
dllname
,
name_table
+
1
,
*
name_table
)
&&
!
strcmp
(
dllname
+
*
name_table
,
".dll"
))
return
descr
;
}
}
return
NULL
;
}
/***********************************************************************
* is_builtin_present
*
* Check if a builtin dll descriptor is present (because we loaded its 32-bit counterpart).
*/
static
BOOL
is_builtin_present
(
LPCSTR
name
)
{
char
dllname
[
20
],
*
p
;
if
(
strlen
(
name
)
>=
sizeof
(
dllname
)
-
4
)
return
FALSE
;
strcpy
(
dllname
,
name
);
p
=
strrchr
(
dllname
,
'.'
);
if
(
!
p
)
strcat
(
dllname
,
".dll"
);
for
(
p
=
dllname
;
*
p
;
p
++
)
*
p
=
FILE_tolower
(
*
p
);
return
(
find_dll_descr
(
dllname
)
!=
NULL
);
}
/***********************************************************************
* __wine_register_dll_16 (KERNEL32.@)
*
* Register a built-in DLL descriptor.
*/
void
__wine_register_dll_16
(
const
BUILTIN16_DESCRIPTOR
*
descr
)
{
int
i
;
for
(
i
=
0
;
i
<
MAX_DLLS
;
i
++
)
{
if
(
builtin_dlls
[
i
])
continue
;
builtin_dlls
[
i
]
=
descr
;
break
;
}
assert
(
i
<
MAX_DLLS
);
}
/***********************************************************************
* __wine_unregister_dll_16 (KERNEL32.@)
*
* Unregister a built-in DLL descriptor.
*/
void
__wine_unregister_dll_16
(
const
BUILTIN16_DESCRIPTOR
*
descr
)
{
int
i
;
for
(
i
=
0
;
i
<
MAX_DLLS
;
i
++
)
{
if
(
builtin_dlls
[
i
]
!=
descr
)
continue
;
builtin_dlls
[
i
]
=
NULL
;
break
;
}
}
/***********************************************************************
* NE_DumpModule
*/
...
...
@@ -785,6 +903,105 @@ static HINSTANCE16 NE_LoadModule( LPCSTR name, BOOL lib_only )
}
/***********************************************************************
* NE_DoLoadBuiltinModule
*
* Load a built-in Win16 module. Helper function for NE_LoadBuiltinModule.
*/
static
HMODULE16
NE_DoLoadBuiltinModule
(
const
BUILTIN16_DESCRIPTOR
*
descr
)
{
NE_MODULE
*
pModule
;
int
minsize
;
SEGTABLEENTRY
*
pSegTable
;
HMODULE16
hModule
;
hModule
=
GLOBAL_CreateBlock
(
GMEM_MOVEABLE
,
descr
->
module_start
,
descr
->
module_size
,
0
,
WINE_LDT_FLAGS_DATA
);
if
(
!
hModule
)
return
0
;
FarSetOwner16
(
hModule
,
hModule
);
pModule
=
(
NE_MODULE
*
)
GlobalLock16
(
hModule
);
pModule
->
self
=
hModule
;
/* NOTE: (Ab)use the hRsrcMap parameter for resource data pointer */
pModule
->
hRsrcMap
=
(
void
*
)
descr
->
rsrc
;
/* Allocate the code segment */
pSegTable
=
NE_SEG_TABLE
(
pModule
);
pSegTable
->
hSeg
=
GLOBAL_CreateBlock
(
GMEM_FIXED
,
descr
->
code_start
,
pSegTable
->
minsize
,
hModule
,
WINE_LDT_FLAGS_CODE
|
WINE_LDT_FLAGS_32BIT
);
if
(
!
pSegTable
->
hSeg
)
return
0
;
patch_code_segment
(
descr
->
code_start
);
pSegTable
++
;
/* Allocate the data segment */
minsize
=
pSegTable
->
minsize
?
pSegTable
->
minsize
:
0x10000
;
minsize
+=
pModule
->
heap_size
;
if
(
minsize
>
0x10000
)
minsize
=
0x10000
;
pSegTable
->
hSeg
=
GlobalAlloc16
(
GMEM_FIXED
,
minsize
);
if
(
!
pSegTable
->
hSeg
)
return
0
;
FarSetOwner16
(
pSegTable
->
hSeg
,
hModule
);
if
(
pSegTable
->
minsize
)
memcpy
(
GlobalLock16
(
pSegTable
->
hSeg
),
descr
->
data_start
,
pSegTable
->
minsize
);
if
(
pModule
->
heap_size
)
LocalInit16
(
GlobalHandleToSel16
(
pSegTable
->
hSeg
),
pSegTable
->
minsize
,
minsize
);
if
(
descr
->
rsrc
)
NE_InitResourceHandler
(
pModule
);
NE_RegisterModule
(
pModule
);
/* make sure the 32-bit library containing this one is loaded too */
LoadLibraryA
(
descr
->
owner
);
return
hModule
;
}
/***********************************************************************
* NE_LoadBuiltinModule
*
* Load a built-in module.
*/
static
HMODULE16
NE_LoadBuiltinModule
(
LPCSTR
name
)
{
const
BUILTIN16_DESCRIPTOR
*
descr
;
char
error
[
256
],
dllname
[
20
],
*
p
;
int
file_exists
;
void
*
handle
;
/* Fix the name in case we have a full path and extension */
if
((
p
=
strrchr
(
name
,
'\\'
)))
name
=
p
+
1
;
if
((
p
=
strrchr
(
name
,
'/'
)))
name
=
p
+
1
;
if
(
strlen
(
name
)
>=
sizeof
(
dllname
)
-
4
)
return
(
HMODULE16
)
2
;
strcpy
(
dllname
,
name
);
p
=
strrchr
(
dllname
,
'.'
);
if
(
!
p
)
strcat
(
dllname
,
".dll"
);
for
(
p
=
dllname
;
*
p
;
p
++
)
*
p
=
FILE_tolower
(
*
p
);
if
((
descr
=
find_dll_descr
(
dllname
)))
return
NE_DoLoadBuiltinModule
(
descr
);
if
((
handle
=
wine_dll_load
(
dllname
,
error
,
sizeof
(
error
),
&
file_exists
)))
{
if
((
descr
=
find_dll_descr
(
dllname
)))
return
NE_DoLoadBuiltinModule
(
descr
);
ERR
(
"loaded .so but dll %s still not found
\n
"
,
dllname
);
}
else
{
if
(
!
file_exists
)
WARN
(
"cannot open .so lib for 16-bit builtin %s: %s
\n
"
,
name
,
error
);
else
ERR
(
"failed to load .so lib for 16-bit builtin %s: %s
\n
"
,
name
,
error
);
}
return
(
HMODULE16
)
2
;
}
/**********************************************************************
* MODULE_LoadModule16
*
...
...
@@ -798,8 +1015,22 @@ static HINSTANCE16 MODULE_LoadModule16( LPCSTR libname, BOOL implicit, BOOL lib_
enum
loadorder_type
loadorder
[
LOADORDER_NTYPES
];
int
i
;
const
char
*
filetype
=
""
;
const
char
*
ptr
;
/* strip path information */
MODULE_GetLoadOrder
(
loadorder
,
libname
,
FALSE
);
if
(
libname
[
0
]
&&
libname
[
1
]
==
':'
)
libname
+=
2
;
/* strip drive specification */
if
((
ptr
=
strrchr
(
libname
,
'\\'
)))
libname
=
ptr
+
1
;
if
((
ptr
=
strrchr
(
libname
,
'/'
)))
libname
=
ptr
+
1
;
if
(
is_builtin_present
(
libname
))
{
TRACE
(
"forcing loadorder to builtin for %s
\n
"
,
debugstr_a
(
libname
)
);
/* force builtin loadorder since the dll is already in memory */
loadorder
[
0
]
=
LOADORDER_BI
;
loadorder
[
1
]
=
LOADORDER_INVALID
;
}
else
MODULE_GetLoadOrder
(
loadorder
,
libname
,
FALSE
);
for
(
i
=
0
;
i
<
LOADORDER_NTYPES
;
i
++
)
{
...
...
@@ -815,7 +1046,7 @@ static HINSTANCE16 MODULE_LoadModule16( LPCSTR libname, BOOL implicit, BOOL lib_
case
LOADORDER_BI
:
TRACE
(
"Trying built-in '%s'
\n
"
,
libname
);
hinst
=
BUILTIN_Load
Module
(
libname
);
hinst
=
NE_LoadBuiltin
Module
(
libname
);
filetype
=
"builtin"
;
break
;
...
...
dlls/ntdll/Makefile.in
View file @
9ca159b6
...
...
@@ -13,7 +13,6 @@ C_SRCS = \
$(TOPOBJDIR)
/files/file.c
\
$(TOPOBJDIR)
/files/profile.c
\
$(TOPOBJDIR)
/files/smb.c
\
$(TOPOBJDIR)
/if1632/builtin.c
\
$(TOPOBJDIR)
/if1632/relay.c
\
$(TOPOBJDIR)
/if1632/snoop.c
\
$(TOPOBJDIR)
/loader/loadorder.c
\
...
...
if1632/builtin.c
deleted
100644 → 0
View file @
1c6a20f3
/*
* Built-in modules
*
* Copyright 1996 Alexandre Julliard
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "config.h"
#include "wine/port.h"
#include <assert.h>
#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include "winbase.h"
#include "wine/winbase16.h"
#include "builtin16.h"
#include "global.h"
#include "file.h"
#include "module.h"
#include "miscemu.h"
#include "stackframe.h"
#include "wine/library.h"
#include "wine/debug.h"
#include "toolhelp.h"
WINE_DEFAULT_DEBUG_CHANNEL
(
module
);
typedef
struct
{
void
*
module_start
;
/* 32-bit address of the module data */
int
module_size
;
/* Size of the module data */
void
*
code_start
;
/* 32-bit address of DLL code */
void
*
data_start
;
/* 32-bit address of DLL data */
const
char
*
owner
;
/* 32-bit dll that contains this dll */
const
void
*
rsrc
;
/* resources data */
}
BUILTIN16_DESCRIPTOR
;
/* Table of all built-in DLLs */
#define MAX_DLLS 50
static
const
BUILTIN16_DESCRIPTOR
*
builtin_dlls
[
MAX_DLLS
];
/* patch all the flat cs references of the code segment if necessary */
inline
static
void
patch_code_segment
(
void
*
code_segment
)
{
#ifdef __i386__
CALLFROM16
*
call
=
code_segment
;
if
(
call
->
flatcs
==
wine_get_cs
())
return
;
/* nothing to patch */
while
(
call
->
pushl
==
0x68
)
{
call
->
flatcs
=
wine_get_cs
();
call
++
;
}
#endif
}
/***********************************************************************
* BUILTIN_DoLoadModule16
*
* Load a built-in Win16 module. Helper function for BUILTIN_LoadModule.
*/
static
HMODULE16
BUILTIN_DoLoadModule16
(
const
BUILTIN16_DESCRIPTOR
*
descr
)
{
NE_MODULE
*
pModule
;
int
minsize
;
SEGTABLEENTRY
*
pSegTable
;
HMODULE16
hModule
;
hModule
=
GLOBAL_CreateBlock
(
GMEM_MOVEABLE
,
descr
->
module_start
,
descr
->
module_size
,
0
,
WINE_LDT_FLAGS_DATA
);
if
(
!
hModule
)
return
0
;
FarSetOwner16
(
hModule
,
hModule
);
pModule
=
(
NE_MODULE
*
)
GlobalLock16
(
hModule
);
pModule
->
self
=
hModule
;
/* NOTE: (Ab)use the hRsrcMap parameter for resource data pointer */
pModule
->
hRsrcMap
=
(
void
*
)
descr
->
rsrc
;
/* Allocate the code segment */
pSegTable
=
NE_SEG_TABLE
(
pModule
);
pSegTable
->
hSeg
=
GLOBAL_CreateBlock
(
GMEM_FIXED
,
descr
->
code_start
,
pSegTable
->
minsize
,
hModule
,
WINE_LDT_FLAGS_CODE
|
WINE_LDT_FLAGS_32BIT
);
if
(
!
pSegTable
->
hSeg
)
return
0
;
patch_code_segment
(
descr
->
code_start
);
pSegTable
++
;
/* Allocate the data segment */
minsize
=
pSegTable
->
minsize
?
pSegTable
->
minsize
:
0x10000
;
minsize
+=
pModule
->
heap_size
;
if
(
minsize
>
0x10000
)
minsize
=
0x10000
;
pSegTable
->
hSeg
=
GlobalAlloc16
(
GMEM_FIXED
,
minsize
);
if
(
!
pSegTable
->
hSeg
)
return
0
;
FarSetOwner16
(
pSegTable
->
hSeg
,
hModule
);
if
(
pSegTable
->
minsize
)
memcpy
(
GlobalLock16
(
pSegTable
->
hSeg
),
descr
->
data_start
,
pSegTable
->
minsize
);
if
(
pModule
->
heap_size
)
LocalInit16
(
GlobalHandleToSel16
(
pSegTable
->
hSeg
),
pSegTable
->
minsize
,
minsize
);
if
(
descr
->
rsrc
)
NE_InitResourceHandler
(
pModule
);
NE_RegisterModule
(
pModule
);
/* make sure the 32-bit library containing this one is loaded too */
LoadLibraryA
(
descr
->
owner
);
return
hModule
;
}
/***********************************************************************
* find_dll_descr
*
* Find a descriptor in the list
*/
static
const
BUILTIN16_DESCRIPTOR
*
find_dll_descr
(
const
char
*
dllname
)
{
int
i
;
for
(
i
=
0
;
i
<
MAX_DLLS
;
i
++
)
{
const
BUILTIN16_DESCRIPTOR
*
descr
=
builtin_dlls
[
i
];
if
(
descr
)
{
NE_MODULE
*
pModule
=
(
NE_MODULE
*
)
descr
->
module_start
;
OFSTRUCT
*
pOfs
=
(
OFSTRUCT
*
)((
LPBYTE
)
pModule
+
pModule
->
fileinfo
);
BYTE
*
name_table
=
(
BYTE
*
)
pModule
+
pModule
->
name_table
;
/* check the dll file name */
if
(
!
FILE_strcasecmp
(
pOfs
->
szPathName
,
dllname
))
return
descr
;
/* check the dll module name (without extension) */
if
(
!
FILE_strncasecmp
(
dllname
,
name_table
+
1
,
*
name_table
)
&&
!
strcmp
(
dllname
+
*
name_table
,
".dll"
))
return
descr
;
}
}
return
NULL
;
}
/***********************************************************************
* BUILTIN_IsPresent
*
* Check if a builtin dll descriptor is present (because we loaded its 32-bit counterpart).
*/
BOOL
BUILTIN_IsPresent
(
LPCSTR
name
)
{
char
dllname
[
20
],
*
p
;
if
(
strlen
(
name
)
>=
sizeof
(
dllname
)
-
4
)
return
FALSE
;
strcpy
(
dllname
,
name
);
p
=
strrchr
(
dllname
,
'.'
);
if
(
!
p
)
strcat
(
dllname
,
".dll"
);
for
(
p
=
dllname
;
*
p
;
p
++
)
*
p
=
FILE_tolower
(
*
p
);
return
(
find_dll_descr
(
dllname
)
!=
NULL
);
}
/***********************************************************************
* BUILTIN_LoadModule
*
* Load a built-in module.
*/
HMODULE16
BUILTIN_LoadModule
(
LPCSTR
name
)
{
const
BUILTIN16_DESCRIPTOR
*
descr
;
char
error
[
256
],
dllname
[
20
],
*
p
;
int
file_exists
;
void
*
handle
;
/* Fix the name in case we have a full path and extension */
if
((
p
=
strrchr
(
name
,
'\\'
)))
name
=
p
+
1
;
if
((
p
=
strrchr
(
name
,
'/'
)))
name
=
p
+
1
;
if
(
strlen
(
name
)
>=
sizeof
(
dllname
)
-
4
)
return
(
HMODULE16
)
2
;
strcpy
(
dllname
,
name
);
p
=
strrchr
(
dllname
,
'.'
);
if
(
!
p
)
strcat
(
dllname
,
".dll"
);
for
(
p
=
dllname
;
*
p
;
p
++
)
*
p
=
FILE_tolower
(
*
p
);
if
((
descr
=
find_dll_descr
(
dllname
)))
return
BUILTIN_DoLoadModule16
(
descr
);
if
((
handle
=
wine_dll_load
(
dllname
,
error
,
sizeof
(
error
),
&
file_exists
)))
{
if
((
descr
=
find_dll_descr
(
dllname
)))
return
BUILTIN_DoLoadModule16
(
descr
);
ERR
(
"loaded .so but dll %s still not found
\n
"
,
dllname
);
}
else
{
if
(
!
file_exists
)
WARN
(
"cannot open .so lib for 16-bit builtin %s: %s
\n
"
,
name
,
error
);
else
ERR
(
"failed to load .so lib for 16-bit builtin %s: %s
\n
"
,
name
,
error
);
}
return
(
HMODULE16
)
2
;
}
/***********************************************************************
* __wine_register_dll_16 (KERNEL32.@)
*
* Register a built-in DLL descriptor.
*/
void
__wine_register_dll_16
(
const
BUILTIN16_DESCRIPTOR
*
descr
)
{
int
i
;
for
(
i
=
0
;
i
<
MAX_DLLS
;
i
++
)
{
if
(
builtin_dlls
[
i
])
continue
;
builtin_dlls
[
i
]
=
descr
;
break
;
}
assert
(
i
<
MAX_DLLS
);
}
/***********************************************************************
* __wine_unregister_dll_16 (KERNEL32.@)
*
* Unregister a built-in DLL descriptor.
*/
void
__wine_unregister_dll_16
(
const
BUILTIN16_DESCRIPTOR
*
descr
)
{
int
i
;
for
(
i
=
0
;
i
<
MAX_DLLS
;
i
++
)
{
if
(
builtin_dlls
[
i
]
!=
descr
)
continue
;
builtin_dlls
[
i
]
=
NULL
;
break
;
}
}
include/module.h
View file @
9ca159b6
...
...
@@ -230,8 +230,4 @@ extern void MODULE_AddLoadOrderOption( const char *option );
extern
NTSTATUS
BUILTIN32_LoadLibraryExA
(
LPCSTR
name
,
DWORD
flags
,
WINE_MODREF
**
);
extern
HMODULE
BUILTIN32_LoadExeModule
(
HMODULE
main
);
/* if1632/builtin.c */
extern
HMODULE16
BUILTIN_LoadModule
(
LPCSTR
name
);
extern
BOOL
BUILTIN_IsPresent
(
LPCSTR
name
);
#endif
/* __WINE_MODULE_H */
loader/loadorder.c
View file @
9ca159b6
...
...
@@ -555,21 +555,10 @@ void MODULE_GetLoadOrder( enum loadorder_type loadorder[], const char *path, BOO
loadorder
[
0
]
=
LOADORDER_INVALID
;
/* in case something bad happens below */
/* Strip path information for 16 bit modules or if the module
* resides in the system directory */
if
(
!
win32
)
{
path
=
get_basename
(
path
);
if
(
BUILTIN_IsPresent
(
path
))
{
TRACE
(
"forcing loadorder to builtin for %s
\n
"
,
debugstr_a
(
path
)
);
/* force builtin loadorder since the dll is already in memory */
loadorder
[
0
]
=
LOADORDER_BI
;
loadorder
[
1
]
=
LOADORDER_INVALID
;
return
;
}
}
else
/* Strip path information if the module resides in the system directory
* (path is already stripped by caller for 16-bit modules)
*/
if
(
win32
)
{
char
sysdir
[
MAX_PATH
+
1
];
if
(
!
GetSystemDirectoryA
(
sysdir
,
MAX_PATH
))
return
;
...
...
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