Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
L
linux
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
linux
Commits
d6e04ae6
Commit
d6e04ae6
authored
Jun 13, 2005
by
Steve French
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[CIFS] CIFS writepage improvements - eliminate double copy
Signed-off-by: Steve French (sfrench@us.ibm.com)
parent
2830077f
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
231 additions
and
76 deletions
+231
-76
fs/cifs/cifsproto.h
fs/cifs/cifsproto.h
+5
-1
fs/cifs/cifssmb.c
fs/cifs/cifssmb.c
+36
-21
fs/cifs/file.c
fs/cifs/file.c
+17
-4
fs/cifs/transport.c
fs/cifs/transport.c
+173
-50
No files found.
fs/cifs/cifsproto.h
View file @
d6e04ae6
...
...
@@ -47,6 +47,10 @@ extern int SendReceive(const unsigned int /* xid */ , struct cifsSesInfo *,
struct
smb_hdr
*
/* input */
,
struct
smb_hdr
*
/* out */
,
int
*
/* bytes returned */
,
const
int
long_op
);
extern
int
SendReceive2
(
const
unsigned
int
/* xid */
,
struct
cifsSesInfo
*
,
struct
smb_hdr
*
/* input */
,
int
hdr_len
,
const
char
*
/* SMB data to send */
,
int
data_len
,
int
*
/* bytes returned */
,
const
int
long_op
);
extern
int
checkSMBhdr
(
struct
smb_hdr
*
smb
,
__u16
mid
);
extern
int
checkSMB
(
struct
smb_hdr
*
smb
,
__u16
mid
,
int
length
);
extern
int
is_valid_oplock_break
(
struct
smb_hdr
*
smb
);
...
...
@@ -222,7 +226,7 @@ extern int CIFSSMBWrite(const int xid, struct cifsTconInfo *tcon,
extern
int
CIFSSMBWrite2
(
const
int
xid
,
struct
cifsTconInfo
*
tcon
,
const
int
netfid
,
const
unsigned
int
count
,
const
__u64
offset
,
unsigned
int
*
nbytes
,
const
char
__user
*
buf
,
const
int
long_op
);
const
char
*
buf
,
const
int
long_op
);
extern
int
CIFSGetSrvInodeNumber
(
const
int
xid
,
struct
cifsTconInfo
*
tcon
,
const
unsigned
char
*
searchName
,
__u64
*
inode_number
,
const
struct
nls_table
*
nls_codepage
,
...
...
fs/cifs/cifssmb.c
View file @
d6e04ae6
...
...
@@ -951,25 +951,23 @@ CIFSSMBWrite(const int xid, struct cifsTconInfo *tcon,
}
#ifdef CONFIG_CIFS_EXPERIMENTAL
int
CIFSSMBWrite2
(
const
int
xid
,
struct
cifsTconInfo
*
tcon
,
int
CIFSSMBWrite2
(
const
int
xid
,
struct
cifsTconInfo
*
tcon
,
const
int
netfid
,
const
unsigned
int
count
,
const
__u64
offset
,
unsigned
int
*
nbytes
,
const
char
__user
*
buf
,
const
__u64
offset
,
unsigned
int
*
nbytes
,
const
char
*
buf
,
const
int
long_op
)
{
int
rc
=
-
EACCES
;
WRITE_REQ
*
pSMB
=
NULL
;
WRITE_RSP
*
pSMBr
=
NULL
;
/*int bytes_returned;*/
unsigned
bytes_sent
;
int
bytes_returned
;
int
smb_hdr_len
;
__u32
bytes_sent
;
__u16
byte_count
;
cERROR
(
1
,(
"write2 at %lld %d bytes"
,
offset
,
count
));
/* BB removeme BB */
rc
=
small_smb_init
(
SMB_COM_WRITE_ANDX
,
14
,
tcon
,
(
void
**
)
&
pSMB
);
if
(
rc
)
return
rc
;
pSMBr
=
(
WRITE_RSP
*
)
pSMB
;
/* BB removeme BB */
/* tcon and ses pointer are checked in smb_init */
if
(
tcon
->
ses
->
server
==
NULL
)
return
-
ECONNABORTED
;
...
...
@@ -981,26 +979,41 @@ int CIFSSMBWrite2(const int xid, struct cifsTconInfo *tcon,
pSMB
->
Reserved
=
0xFFFFFFFF
;
pSMB
->
WriteMode
=
0
;
pSMB
->
Remaining
=
0
;
bytes_sent
=
(
tcon
->
ses
->
server
->
maxBuf
-
MAX_CIFS_HDR_SIZE
)
&
~
0xFF
;
/* Can increase buffer size if buffer is big enough in some cases - ie
can send more if LARGE_WRITE_X capability returned by the server and if
our buffer is big enough or if we convert to iovecs on socket writes
and eliminate the copy to the CIFS buffer */
if
(
tcon
->
ses
->
capabilities
&
CAP_LARGE_WRITE_X
)
{
bytes_sent
=
min_t
(
const
unsigned
int
,
CIFSMaxBufSize
,
count
);
}
else
{
bytes_sent
=
(
tcon
->
ses
->
server
->
maxBuf
-
MAX_CIFS_HDR_SIZE
)
&
~
0xFF
;
}
if
(
bytes_sent
>
count
)
bytes_sent
=
count
;
pSMB
->
DataLengthHigh
=
0
;
pSMB
->
DataOffset
=
cpu_to_le16
(
offsetof
(
struct
smb_com_write_req
,
Data
)
-
4
);
byte_count
=
bytes_sent
+
1
/* pad */
;
pSMB
->
DataLengthLow
=
cpu_to_le16
(
bytes_sent
);
pSMB
->
DataLengthHigh
=
0
;
pSMB
->
hdr
.
smb_buf_length
+=
byte_count
;
byte_count
=
bytes_sent
+
1
/* pad */
;
/* BB fix this for sends > 64K */
pSMB
->
DataLengthLow
=
cpu_to_le16
(
bytes_sent
&
0xFFFF
);
pSMB
->
DataLengthHigh
=
cpu_to_le16
(
bytes_sent
>>
16
);
smb_hdr_len
=
pSMB
->
hdr
.
smb_buf_length
+
1
;
/* hdr + 1 byte pad */
pSMB
->
hdr
.
smb_buf_length
+=
bytes_sent
+
1
;
pSMB
->
ByteCount
=
cpu_to_le16
(
byte_count
);
/* rc = SendReceive2(xid, tcon->ses, (struct smb_hdr *) pSMB
,
(struct smb_hdr *) pSMBr, buf, buflen, &bytes_returned, long_op); */
/* BB fixme BB */
rc
=
SendReceive2
(
xid
,
tcon
->
ses
,
(
struct
smb_hdr
*
)
pSMB
,
smb_hdr_len
,
buf
,
bytes_sent
,
&
bytes_returned
,
long_op
);
if
(
rc
)
{
cFYI
(
1
,
(
"Send error in write
2 (large write)
= %d"
,
rc
));
cFYI
(
1
,
(
"Send error in write = %d"
,
rc
));
*
nbytes
=
0
;
}
else
*
nbytes
=
le16_to_cpu
(
pSMBr
->
Count
);
}
else
{
WRITE_RSP
*
pSMBr
=
(
WRITE_RSP
*
)
pSMB
;
*
nbytes
=
le16_to_cpu
(
pSMBr
->
CountHigh
);
*
nbytes
=
(
*
nbytes
)
<<
16
;
*
nbytes
+=
le16_to_cpu
(
pSMBr
->
Count
);
}
cifs_small_buf_release
(
pSMB
);
...
...
@@ -1009,6 +1022,8 @@ int CIFSSMBWrite2(const int xid, struct cifsTconInfo *tcon,
return
rc
;
}
#endif
/* CIFS_EXPERIMENTAL */
int
...
...
fs/cifs/file.c
View file @
d6e04ae6
...
...
@@ -791,9 +791,8 @@ static ssize_t cifs_write(struct file *file, const char *write_data,
pTcon
=
cifs_sb
->
tcon
;
/* cFYI(1,
(" write %d bytes to offset %lld of %s", write_size,
*poffset, file->f_dentry->d_name.name)); */
cFYI
(
1
,(
" write %d bytes to offset %lld of %s"
,
write_size
,
*
poffset
,
file
->
f_dentry
->
d_name
.
name
));
/* BB removeme BB */
if
(
file
->
private_data
==
NULL
)
return
-
EBADF
;
...
...
@@ -846,7 +845,21 @@ static ssize_t cifs_write(struct file *file, const char *write_data,
if
(
rc
!=
0
)
break
;
}
#ifdef CIFS_EXPERIMENTAL
/* BB FIXME We can not sign across two buffers yet */
cERROR
(
1
,(
"checking signing"
));
/* BB removeme BB */
if
(
pTcon
->
ses
->
server
->
secMode
&
(
SECMODE_SIGN_REQUIRED
|
SECMODE_SIGN_ENABLED
)
==
0
)
rc
=
CIFSSMBWrite2
(
xid
,
pTcon
,
open_file
->
netfid
,
min_t
(
const
int
,
cifs_sb
->
wsize
,
write_size
-
total_written
),
*
poffset
,
&
bytes_written
,
write_data
+
total_written
,
long_op
);
}
else
/* BB FIXME fixup indentation of line below */
#endif
rc
=
CIFSSMBWrite
(
xid
,
pTcon
,
open_file
->
netfid
,
min_t
(
const
int
,
cifs_sb
->
wsize
,
...
...
fs/cifs/transport.c
View file @
d6e04ae6
...
...
@@ -49,7 +49,8 @@ AllocMidQEntry(struct smb_hdr *smb_buffer, struct cifsSesInfo *ses)
return
NULL
;
}
temp
=
(
struct
mid_q_entry
*
)
mempool_alloc
(
cifs_mid_poolp
,
SLAB_KERNEL
|
SLAB_NOFS
);
temp
=
(
struct
mid_q_entry
*
)
mempool_alloc
(
cifs_mid_poolp
,
SLAB_KERNEL
|
SLAB_NOFS
);
if
(
temp
==
NULL
)
return
temp
;
else
{
...
...
@@ -179,27 +180,24 @@ smb_send(struct socket *ssocket, struct smb_hdr *smb_buffer,
return
rc
;
}
#ifdef CIFS_EXPERIMENTAL
/* BB finish off this function, adding support for writing set of pages as iovec */
/* and also adding support for operations that need to parse the response smb */
int
smb_sendv
(
struct
socket
*
ssocket
,
struct
smb_hdr
*
smb_buffer
,
unsigned
int
smb_buf_length
,
struct
kvec
*
write_vector
/* page list */
,
struct
sockaddr
*
sin
)
#ifdef CONFIG_CIFS_EXPERIMENTAL
static
int
smb_send2
(
struct
socket
*
ssocket
,
struct
smb_hdr
*
smb_buffer
,
unsigned
int
smb_hdr_length
,
const
char
*
data
,
unsigned
int
datalen
,
struct
sockaddr
*
sin
)
{
int
rc
=
0
;
int
i
=
0
;
struct
msghdr
smb_msg
;
number_of_pages
+=
1
;
/* account for SMB header */
struct
kvec
*
piov
=
kmalloc
(
number_of_pages
*
sizeof
(
struct
kvec
));
unsigned
len
=
smb_buf_length
+
4
;
struct
kvec
iov
[
2
];
unsigned
len
=
smb_hdr_length
+
4
;
if
(
ssocket
==
NULL
)
return
-
ENOTSOCK
;
/* BB eventually add reconnect code here */
iov
.
iov_base
=
smb_buffer
;
iov
.
iov_len
=
len
;
iov
[
0
].
iov_base
=
smb_buffer
;
iov
[
0
].
iov_len
=
len
;
iov
[
1
].
iov_base
=
data
;
iov
[
2
].
iov_len
=
datalen
;
smb_msg
.
msg_name
=
sin
;
smb_msg
.
msg_namelen
=
sizeof
(
struct
sockaddr
);
smb_msg
.
msg_control
=
NULL
;
...
...
@@ -212,12 +210,11 @@ smb_sendv(struct socket *ssocket, struct smb_hdr *smb_buffer,
Flags2 is converted in SendReceive */
smb_buffer
->
smb_buf_length
=
cpu_to_be32
(
smb_buffer
->
smb_buf_length
);
cFYI
(
1
,
(
"Sending smb of length %d "
,
smb_buf_length
));
cFYI
(
1
,
(
"Sending smb of length %d "
,
len
+
datalen
));
dump_smb
(
smb_buffer
,
len
);
while
(
len
>
0
)
{
rc
=
kernel_sendmsg
(
ssocket
,
&
smb_msg
,
&
iov
,
number_of_pages
,
len
);
while
(
len
+
datalen
>
0
)
{
rc
=
kernel_sendmsg
(
ssocket
,
&
smb_msg
,
iov
,
2
,
len
);
if
((
rc
==
-
ENOSPC
)
||
(
rc
==
-
EAGAIN
))
{
i
++
;
if
(
i
>
60
)
{
...
...
@@ -232,9 +229,22 @@ smb_sendv(struct socket *ssocket, struct smb_hdr *smb_buffer,
}
if
(
rc
<
0
)
break
;
iov
.
iov_base
+=
rc
;
iov
.
iov_len
-=
rc
;
if
(
iov
[
0
].
iov_len
>
0
)
{
if
(
rc
>=
len
)
{
iov
[
0
].
iov_len
=
0
;
rc
-=
len
;
}
else
{
/* some of hdr was not sent */
len
-=
rc
;
iov
[
0
].
iov_len
-=
rc
;
iov
[
0
].
iov_base
+=
rc
;
continue
;
}
}
if
((
iov
[
0
].
iov_len
==
0
)
&&
(
rc
>
0
)){
iov
[
1
].
iov_base
+=
rc
;
iov
[
1
].
iov_len
-=
rc
;
datalen
-=
rc
;
}
}
if
(
rc
<
0
)
{
...
...
@@ -246,14 +256,15 @@ smb_sendv(struct socket *ssocket, struct smb_hdr *smb_buffer,
return
rc
;
}
int
CIFSSendRcv
(
const
unsigned
int
xid
,
struct
cifsSesInfo
*
ses
,
struct
smb_hdr
*
in_buf
,
struct
kvec
*
write_vector
/* page list */
,
int
*
pbytes_returned
,
const
int
long_op
)
SendReceive2
(
const
unsigned
int
xid
,
struct
cifsSesInfo
*
ses
,
struct
smb_hdr
*
in_buf
,
int
hdrlen
,
const
char
*
data
,
int
datalen
,
int
*
pbytes_returned
,
const
int
long_op
)
{
int
rc
=
0
;
unsigned
long
timeout
=
15
*
HZ
;
struct
mid_q_entry
*
midQ
=
NULL
;
unsigned
int
receive_len
;
unsigned
long
timeout
;
struct
mid_q_entry
*
midQ
;
if
(
ses
==
NULL
)
{
cERROR
(
1
,(
"Null smb session"
));
...
...
@@ -263,14 +274,8 @@ CIFSSendRcv(const unsigned int xid, struct cifsSesInfo *ses,
cERROR
(
1
,(
"Null tcp session"
));
return
-
EIO
;
}
if
(
pbytes_returned
==
NULL
)
return
-
EIO
;
else
*
pbytes_returned
=
0
;
if
(
ses
->
server
->
tcpStatus
==
C
IFS_EXITING
)
if
(
ses
->
server
->
tcpStatus
==
C
ifsExiting
)
return
-
ENOENT
;
/* Ensure that we do not send more than 50 overlapping requests
...
...
@@ -282,7 +287,8 @@ CIFSSendRcv(const unsigned int xid, struct cifsSesInfo *ses,
}
else
{
spin_lock
(
&
GlobalMid_Lock
);
while
(
1
)
{
if
(
atomic_read
(
&
ses
->
server
->
inFlight
)
>=
cifs_max_pending
){
if
(
atomic_read
(
&
ses
->
server
->
inFlight
)
>=
cifs_max_pending
){
spin_unlock
(
&
GlobalMid_Lock
);
wait_event
(
ses
->
server
->
request_q
,
atomic_read
(
&
ses
->
server
->
inFlight
)
...
...
@@ -314,17 +320,17 @@ CIFSSendRcv(const unsigned int xid, struct cifsSesInfo *ses,
if
(
ses
->
server
->
tcpStatus
==
CifsExiting
)
{
rc
=
-
ENOENT
;
goto
cifs_out_label
;
goto
out_unlock2
;
}
else
if
(
ses
->
server
->
tcpStatus
==
CifsNeedReconnect
)
{
cFYI
(
1
,(
"tcp session dead - return to caller to retry"
));
rc
=
-
EAGAIN
;
goto
cifs_out_label
;
goto
out_unlock2
;
}
else
if
(
ses
->
status
!=
CifsGood
)
{
/* check if SMB session is bad because we are setting it up */
if
((
in_buf
->
Command
!=
SMB_COM_SESSION_SETUP_ANDX
)
&&
(
in_buf
->
Command
!=
SMB_COM_NEGOTIATE
))
{
rc
=
-
EAGAIN
;
goto
cifs_out_label
;
goto
out_unlock2
;
}
/* else ok - we are setting up session */
}
midQ
=
AllocMidQEntry
(
in_buf
,
ses
);
...
...
@@ -352,13 +358,12 @@ CIFSSendRcv(const unsigned int xid, struct cifsSesInfo *ses,
return
-
EIO
;
}
/* BB can we sign efficiently in this path?
*/
rc
=
cifs_sign_smb
(
in_buf
,
ses
->
server
,
&
midQ
->
sequence_number
);
/* BB FIXME
*/
/* rc = cifs_sign_smb2(in_buf, data, ses->server, &midQ->sequence_number); */
midQ
->
midState
=
MID_REQUEST_SUBMITTED
;
/* rc = smb_sendv(ses->server->ssocket, in_buf, in_buf->smb_buf_length,
piovec,
(struct sockaddr *) &(ses->server->addr.sockAddr));*/
rc
=
smb_send2
(
ses
->
server
->
ssocket
,
in_buf
,
hdrlen
,
data
,
datalen
,
(
struct
sockaddr
*
)
&
(
ses
->
server
->
addr
.
sockAddr
));
if
(
rc
<
0
)
{
DeleteMidQEntry
(
midQ
);
up
(
&
ses
->
server
->
tcpSem
);
...
...
@@ -370,8 +375,118 @@ CIFSSendRcv(const unsigned int xid, struct cifsSesInfo *ses,
return
rc
;
}
else
up
(
&
ses
->
server
->
tcpSem
);
cifs_out_label:
if
(
midQ
)
if
(
long_op
==
-
1
)
goto
cifs_no_response_exit2
;
else
if
(
long_op
==
2
)
/* writes past end of file can take loong time */
timeout
=
300
*
HZ
;
else
if
(
long_op
==
1
)
timeout
=
45
*
HZ
;
/* should be greater than
servers oplock break timeout (about 43 seconds) */
else
if
(
long_op
>
2
)
{
timeout
=
MAX_SCHEDULE_TIMEOUT
;
}
else
timeout
=
15
*
HZ
;
/* wait for 15 seconds or until woken up due to response arriving or
due to last connection to this server being unmounted */
if
(
signal_pending
(
current
))
{
/* if signal pending do not hold up user for full smb timeout
but we still give response a change to complete */
timeout
=
2
*
HZ
;
}
/* No user interrupts in wait - wreaks havoc with performance */
if
(
timeout
!=
MAX_SCHEDULE_TIMEOUT
)
{
timeout
+=
jiffies
;
wait_event
(
ses
->
server
->
response_q
,
(
!
(
midQ
->
midState
&
MID_REQUEST_SUBMITTED
))
||
time_after
(
jiffies
,
timeout
)
||
((
ses
->
server
->
tcpStatus
!=
CifsGood
)
&&
(
ses
->
server
->
tcpStatus
!=
CifsNew
)));
}
else
{
wait_event
(
ses
->
server
->
response_q
,
(
!
(
midQ
->
midState
&
MID_REQUEST_SUBMITTED
))
||
((
ses
->
server
->
tcpStatus
!=
CifsGood
)
&&
(
ses
->
server
->
tcpStatus
!=
CifsNew
)));
}
spin_lock
(
&
GlobalMid_Lock
);
if
(
midQ
->
resp_buf
)
{
spin_unlock
(
&
GlobalMid_Lock
);
receive_len
=
be32_to_cpu
(
*
(
__be32
*
)
midQ
->
resp_buf
);
}
else
{
cERROR
(
1
,(
"No response buffer"
));
if
(
midQ
->
midState
==
MID_REQUEST_SUBMITTED
)
{
if
(
ses
->
server
->
tcpStatus
==
CifsExiting
)
rc
=
-
EHOSTDOWN
;
else
{
ses
->
server
->
tcpStatus
=
CifsNeedReconnect
;
midQ
->
midState
=
MID_RETRY_NEEDED
;
}
}
if
(
rc
!=
-
EHOSTDOWN
)
{
if
(
midQ
->
midState
==
MID_RETRY_NEEDED
)
{
rc
=
-
EAGAIN
;
cFYI
(
1
,(
"marking request for retry"
));
}
else
{
rc
=
-
EIO
;
}
}
spin_unlock
(
&
GlobalMid_Lock
);
DeleteMidQEntry
(
midQ
);
/* If not lock req, update # of requests on wire to server */
if
(
long_op
<
3
)
{
atomic_dec
(
&
ses
->
server
->
inFlight
);
wake_up
(
&
ses
->
server
->
request_q
);
}
return
rc
;
}
if
(
receive_len
>
CIFSMaxBufSize
+
MAX_CIFS_HDR_SIZE
)
{
cERROR
(
1
,
(
"Frame too large received. Length: %d Xid: %d"
,
receive_len
,
xid
));
rc
=
-
EIO
;
}
else
{
/* rcvd frame is ok */
if
(
midQ
->
resp_buf
&&
(
midQ
->
midState
==
MID_RESPONSE_RECEIVED
))
{
in_buf
->
smb_buf_length
=
receive_len
;
/* BB verify that length would not overrun small buf */
memcpy
((
char
*
)
in_buf
+
4
,
(
char
*
)
midQ
->
resp_buf
+
4
,
receive_len
);
dump_smb
(
in_buf
,
80
);
/* convert the length into a more usable form */
if
((
receive_len
>
24
)
&&
(
ses
->
server
->
secMode
&
(
SECMODE_SIGN_REQUIRED
|
SECMODE_SIGN_ENABLED
)))
{
rc
=
cifs_verify_signature
(
in_buf
,
ses
->
server
->
mac_signing_key
,
midQ
->
sequence_number
+
1
);
if
(
rc
)
{
cERROR
(
1
,(
"Unexpected SMB signature"
));
/* BB FIXME add code to kill session */
}
}
*
pbytes_returned
=
in_buf
->
smb_buf_length
;
/* BB special case reconnect tid and uid here? */
rc
=
map_smb_to_linux_error
(
in_buf
);
/* convert ByteCount if necessary */
if
(
receive_len
>=
sizeof
(
struct
smb_hdr
)
-
4
/* do not count RFC1001 header */
+
(
2
*
in_buf
->
WordCount
)
+
2
/* bcc */
)
BCC
(
in_buf
)
=
le16_to_cpu
(
BCC
(
in_buf
));
}
else
{
rc
=
-
EIO
;
cFYI
(
1
,(
"Bad MID state? "
));
}
}
cifs_no_response_exit2:
DeleteMidQEntry
(
midQ
);
if
(
long_op
<
3
)
{
...
...
@@ -380,9 +495,17 @@ CIFSSendRcv(const unsigned int xid, struct cifsSesInfo *ses,
}
return
rc
;
}
out_unlock2:
up
(
&
ses
->
server
->
tcpSem
);
/* If not lock req, update # of requests on wire to server */
if
(
long_op
<
3
)
{
atomic_dec
(
&
ses
->
server
->
inFlight
);
wake_up
(
&
ses
->
server
->
request_q
);
}
return
rc
;
}
#endif
/* CIFS_EXPERIMENTAL */
int
...
...
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