Commit 887feae3 authored by Deepa Dinamani's avatar Deepa Dinamani Committed by David S. Miller

socket: Add SO_TIMESTAMP[NS]_NEW

Add SO_TIMESTAMP_NEW and SO_TIMESTAMPNS_NEW variants of
socket timestamp options.
These are the y2038 safe versions of the SO_TIMESTAMP_OLD
and SO_TIMESTAMPNS_OLD for all architectures.

Note that the format of scm_timestamping.ts[0] is not changed
in this patch.
Signed-off-by: default avatarDeepa Dinamani <deepa.kernel@gmail.com>
Acked-by: default avatarWillem de Bruijn <willemb@google.com>
Cc: jejb@parisc-linux.org
Cc: ralf@linux-mips.org
Cc: rth@twiddle.net
Cc: linux-alpha@vger.kernel.org
Cc: linux-mips@linux-mips.org
Cc: linux-parisc@vger.kernel.org
Cc: linux-rdma@vger.kernel.org
Cc: netdev@vger.kernel.org
Cc: sparclinux@vger.kernel.org
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 98bb03c8
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
#define _UAPI_ASM_SOCKET_H #define _UAPI_ASM_SOCKET_H
#include <asm/sockios.h> #include <asm/sockios.h>
#include <asm/bitsperlong.h>
/* For setsockopt(2) */ /* For setsockopt(2) */
/* /*
...@@ -114,10 +115,19 @@ ...@@ -114,10 +115,19 @@
#define SO_TIMESTAMPNS_OLD 35 #define SO_TIMESTAMPNS_OLD 35
#define SO_TIMESTAMPING_OLD 37 #define SO_TIMESTAMPING_OLD 37
#define SO_TIMESTAMP_NEW 63
#define SO_TIMESTAMPNS_NEW 64
#if !defined(__KERNEL__) #if !defined(__KERNEL__)
#define SO_TIMESTAMP SO_TIMESTAMP_OLD #if __BITS_PER_LONG == 64
#define SO_TIMESTAMPNS SO_TIMESTAMPNS_OLD #define SO_TIMESTAMP SO_TIMESTAMP_OLD
#define SO_TIMESTAMPNS SO_TIMESTAMPNS_OLD
#else
#define SO_TIMESTAMP (sizeof(time_t) == sizeof(__kernel_long_t) ? SO_TIMESTAMP_OLD : SO_TIMESTAMP_NEW)
#define SO_TIMESTAMPNS (sizeof(time_t) == sizeof(__kernel_long_t) ? SO_TIMESTAMPNS_OLD : SO_TIMESTAMPNS_NEW)
#endif
#define SO_TIMESTAMPING SO_TIMESTAMPING_OLD #define SO_TIMESTAMPING SO_TIMESTAMPING_OLD
#define SCM_TIMESTAMP SO_TIMESTAMP #define SCM_TIMESTAMP SO_TIMESTAMP
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#define _UAPI_ASM_SOCKET_H #define _UAPI_ASM_SOCKET_H
#include <asm/sockios.h> #include <asm/sockios.h>
#include <asm/bitsperlong.h>
/* /*
* For setsockopt(2) * For setsockopt(2)
...@@ -125,10 +126,19 @@ ...@@ -125,10 +126,19 @@
#define SO_TIMESTAMPNS_OLD 35 #define SO_TIMESTAMPNS_OLD 35
#define SO_TIMESTAMPING_OLD 37 #define SO_TIMESTAMPING_OLD 37
#define SO_TIMESTAMP_NEW 63
#define SO_TIMESTAMPNS_NEW 64
#if !defined(__KERNEL__) #if !defined(__KERNEL__)
#define SO_TIMESTAMP SO_TIMESTAMP_OLD #if __BITS_PER_LONG == 64
#define SO_TIMESTAMPNS SO_TIMESTAMPNS_OLD #define SO_TIMESTAMP SO_TIMESTAMP_OLD
#define SO_TIMESTAMPNS SO_TIMESTAMPNS_OLD
#else
#define SO_TIMESTAMP (sizeof(time_t) == sizeof(__kernel_long_t) ? SO_TIMESTAMP_OLD : SO_TIMESTAMP_NEW)
#define SO_TIMESTAMPNS (sizeof(time_t) == sizeof(__kernel_long_t) ? SO_TIMESTAMPNS_OLD : SO_TIMESTAMPNS_NEW)
#endif
#define SO_TIMESTAMPING SO_TIMESTAMPING_OLD #define SO_TIMESTAMPING SO_TIMESTAMPING_OLD
#define SCM_TIMESTAMP SO_TIMESTAMP #define SCM_TIMESTAMP SO_TIMESTAMP
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
#define _UAPI_ASM_SOCKET_H #define _UAPI_ASM_SOCKET_H
#include <asm/sockios.h> #include <asm/sockios.h>
#include <asm/bitsperlong.h>
/* For setsockopt(2) */ /* For setsockopt(2) */
#define SOL_SOCKET 0xffff #define SOL_SOCKET 0xffff
...@@ -106,10 +107,19 @@ ...@@ -106,10 +107,19 @@
#define SO_TIMESTAMPNS_OLD 0x4013 #define SO_TIMESTAMPNS_OLD 0x4013
#define SO_TIMESTAMPING_OLD 0x4020 #define SO_TIMESTAMPING_OLD 0x4020
#define SO_TIMESTAMP_NEW 0x4038
#define SO_TIMESTAMPNS_NEW 0x4039
#if !defined(__KERNEL__) #if !defined(__KERNEL__)
#define SO_TIMESTAMP SO_TIMESTAMP_OLD #if __BITS_PER_LONG == 64
#define SO_TIMESTAMPNS SO_TIMESTAMPNS_OLD #define SO_TIMESTAMP SO_TIMESTAMP_OLD
#define SO_TIMESTAMPNS SO_TIMESTAMPNS_OLD
#else
#define SO_TIMESTAMP (sizeof(time_t) == sizeof(__kernel_long_t) ? SO_TIMESTAMP_OLD : SO_TIMESTAMP_NEW)
#define SO_TIMESTAMPNS (sizeof(time_t) == sizeof(__kernel_long_t) ? SO_TIMESTAMPNS_OLD : SO_TIMESTAMPNS_NEW)
#endif
#define SO_TIMESTAMPING SO_TIMESTAMPING_OLD #define SO_TIMESTAMPING SO_TIMESTAMPING_OLD
#define SCM_TIMESTAMP SO_TIMESTAMP #define SCM_TIMESTAMP SO_TIMESTAMP
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
#define _ASM_SOCKET_H #define _ASM_SOCKET_H
#include <asm/sockios.h> #include <asm/sockios.h>
#include <asm/bitsperlong.h>
/* For setsockopt(2) */ /* For setsockopt(2) */
#define SOL_SOCKET 0xffff #define SOL_SOCKET 0xffff
...@@ -107,10 +108,19 @@ ...@@ -107,10 +108,19 @@
#define SO_TIMESTAMPNS_OLD 0x0021 #define SO_TIMESTAMPNS_OLD 0x0021
#define SO_TIMESTAMPING_OLD 0x0023 #define SO_TIMESTAMPING_OLD 0x0023
#define SO_TIMESTAMP_NEW 0x0041
#define SO_TIMESTAMPNS_NEW 0x0042
#if !defined(__KERNEL__) #if !defined(__KERNEL__)
#define SO_TIMESTAMP SO_TIMESTAMP_OLD #if __BITS_PER_LONG == 64
#define SO_TIMESTAMPNS SO_TIMESTAMPNS_OLD #define SO_TIMESTAMP SO_TIMESTAMP_OLD
#define SO_TIMESTAMPNS SO_TIMESTAMPNS_OLD
#else
#define SO_TIMESTAMP (sizeof(time_t) == sizeof(__kernel_long_t) ? SO_TIMESTAMP_OLD : SO_TIMESTAMP_NEW)
#define SO_TIMESTAMPNS (sizeof(time_t) == sizeof(__kernel_long_t) ? SO_TIMESTAMPNS_OLD : SO_TIMESTAMPNS_NEW)
#endif
#define SO_TIMESTAMPING SO_TIMESTAMPING_OLD #define SO_TIMESTAMPING SO_TIMESTAMPING_OLD
#define SCM_TIMESTAMP SO_TIMESTAMP #define SCM_TIMESTAMP SO_TIMESTAMP
......
...@@ -3498,12 +3498,30 @@ static inline void skb_get_timestamp(const struct sk_buff *skb, ...@@ -3498,12 +3498,30 @@ static inline void skb_get_timestamp(const struct sk_buff *skb,
*stamp = ns_to_kernel_old_timeval(skb->tstamp); *stamp = ns_to_kernel_old_timeval(skb->tstamp);
} }
static inline void skb_get_new_timestamp(const struct sk_buff *skb,
struct __kernel_sock_timeval *stamp)
{
struct timespec64 ts = ktime_to_timespec64(skb->tstamp);
stamp->tv_sec = ts.tv_sec;
stamp->tv_usec = ts.tv_nsec / 1000;
}
static inline void skb_get_timestampns(const struct sk_buff *skb, static inline void skb_get_timestampns(const struct sk_buff *skb,
struct timespec *stamp) struct timespec *stamp)
{ {
*stamp = ktime_to_timespec(skb->tstamp); *stamp = ktime_to_timespec(skb->tstamp);
} }
static inline void skb_get_new_timestampns(const struct sk_buff *skb,
struct __kernel_timespec *stamp)
{
struct timespec64 ts = ktime_to_timespec64(skb->tstamp);
stamp->tv_sec = ts.tv_sec;
stamp->tv_nsec = ts.tv_nsec;
}
static inline void __net_timestamp(struct sk_buff *skb) static inline void __net_timestamp(struct sk_buff *skb)
{ {
skb->tstamp = ktime_get_real(); skb->tstamp = ktime_get_real();
......
...@@ -805,6 +805,7 @@ enum sock_flags { ...@@ -805,6 +805,7 @@ enum sock_flags {
SOCK_RCU_FREE, /* wait rcu grace period in sk_destruct() */ SOCK_RCU_FREE, /* wait rcu grace period in sk_destruct() */
SOCK_TXTIME, SOCK_TXTIME,
SOCK_XDP, /* XDP is attached */ SOCK_XDP, /* XDP is attached */
SOCK_TSTAMP_NEW, /* Indicates 64 bit timestamps always */
}; };
#define SK_FLAGS_TIMESTAMP ((1UL << SOCK_TIMESTAMP) | (1UL << SOCK_TIMESTAMPING_RX_SOFTWARE)) #define SK_FLAGS_TIMESTAMP ((1UL << SOCK_TIMESTAMP) | (1UL << SOCK_TIMESTAMPING_RX_SOFTWARE))
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
#define __ASM_GENERIC_SOCKET_H #define __ASM_GENERIC_SOCKET_H
#include <asm/sockios.h> #include <asm/sockios.h>
#include <asm/bitsperlong.h>
/* For setsockopt(2) */ /* For setsockopt(2) */
#define SOL_SOCKET 1 #define SOL_SOCKET 1
...@@ -109,10 +110,20 @@ ...@@ -109,10 +110,20 @@
#define SO_TIMESTAMPNS_OLD 35 #define SO_TIMESTAMPNS_OLD 35
#define SO_TIMESTAMPING_OLD 37 #define SO_TIMESTAMPING_OLD 37
#define SO_TIMESTAMP_NEW 63
#define SO_TIMESTAMPNS_NEW 64
#if !defined(__KERNEL__) #if !defined(__KERNEL__)
#define SO_TIMESTAMP SO_TIMESTAMP_OLD #if __BITS_PER_LONG == 64 || (defined(__x86_64__) && defined(__ILP32__))
#define SO_TIMESTAMPNS SO_TIMESTAMPNS_OLD /* on 64-bit and x32, avoid the ?: operator */
#define SO_TIMESTAMP SO_TIMESTAMP_OLD
#define SO_TIMESTAMPNS SO_TIMESTAMPNS_OLD
#else
#define SO_TIMESTAMP (sizeof(time_t) == sizeof(__kernel_long_t) ? SO_TIMESTAMP_OLD : SO_TIMESTAMP_NEW)
#define SO_TIMESTAMPNS (sizeof(time_t) == sizeof(__kernel_long_t) ? SO_TIMESTAMPNS_OLD : SO_TIMESTAMPNS_NEW)
#endif
#define SO_TIMESTAMPING SO_TIMESTAMPING_OLD #define SO_TIMESTAMPING SO_TIMESTAMPING_OLD
#define SCM_TIMESTAMP SO_TIMESTAMP #define SCM_TIMESTAMP SO_TIMESTAMP
......
...@@ -868,9 +868,16 @@ int sock_setsockopt(struct socket *sock, int level, int optname, ...@@ -868,9 +868,16 @@ int sock_setsockopt(struct socket *sock, int level, int optname,
break; break;
case SO_TIMESTAMP_OLD: case SO_TIMESTAMP_OLD:
case SO_TIMESTAMP_NEW:
case SO_TIMESTAMPNS_OLD: case SO_TIMESTAMPNS_OLD:
case SO_TIMESTAMPNS_NEW:
if (valbool) { if (valbool) {
if (optname == SO_TIMESTAMP_OLD) if (optname == SO_TIMESTAMP_NEW || optname == SO_TIMESTAMPNS_NEW)
sock_set_flag(sk, SOCK_TSTAMP_NEW);
else
sock_reset_flag(sk, SOCK_TSTAMP_NEW);
if (optname == SO_TIMESTAMP_OLD || optname == SO_TIMESTAMP_NEW)
sock_reset_flag(sk, SOCK_RCVTSTAMPNS); sock_reset_flag(sk, SOCK_RCVTSTAMPNS);
else else
sock_set_flag(sk, SOCK_RCVTSTAMPNS); sock_set_flag(sk, SOCK_RCVTSTAMPNS);
...@@ -879,6 +886,7 @@ int sock_setsockopt(struct socket *sock, int level, int optname, ...@@ -879,6 +886,7 @@ int sock_setsockopt(struct socket *sock, int level, int optname,
} else { } else {
sock_reset_flag(sk, SOCK_RCVTSTAMP); sock_reset_flag(sk, SOCK_RCVTSTAMP);
sock_reset_flag(sk, SOCK_RCVTSTAMPNS); sock_reset_flag(sk, SOCK_RCVTSTAMPNS);
sock_reset_flag(sk, SOCK_TSTAMP_NEW);
} }
break; break;
...@@ -1245,11 +1253,20 @@ int sock_getsockopt(struct socket *sock, int level, int optname, ...@@ -1245,11 +1253,20 @@ int sock_getsockopt(struct socket *sock, int level, int optname,
case SO_TIMESTAMP_OLD: case SO_TIMESTAMP_OLD:
v.val = sock_flag(sk, SOCK_RCVTSTAMP) && v.val = sock_flag(sk, SOCK_RCVTSTAMP) &&
!sock_flag(sk, SOCK_TSTAMP_NEW) &&
!sock_flag(sk, SOCK_RCVTSTAMPNS); !sock_flag(sk, SOCK_RCVTSTAMPNS);
break; break;
case SO_TIMESTAMPNS_OLD: case SO_TIMESTAMPNS_OLD:
v.val = sock_flag(sk, SOCK_RCVTSTAMPNS); v.val = sock_flag(sk, SOCK_RCVTSTAMPNS) && !sock_flag(sk, SOCK_TSTAMP_NEW);
break;
case SO_TIMESTAMP_NEW:
v.val = sock_flag(sk, SOCK_RCVTSTAMP) && sock_flag(sk, SOCK_TSTAMP_NEW);
break;
case SO_TIMESTAMPNS_NEW:
v.val = sock_flag(sk, SOCK_RCVTSTAMPNS) && sock_flag(sk, SOCK_TSTAMP_NEW);
break; break;
case SO_TIMESTAMPING_OLD: case SO_TIMESTAMPING_OLD:
......
...@@ -1861,20 +1861,37 @@ static void tcp_update_recv_tstamps(struct sk_buff *skb, ...@@ -1861,20 +1861,37 @@ static void tcp_update_recv_tstamps(struct sk_buff *skb,
static void tcp_recv_timestamp(struct msghdr *msg, const struct sock *sk, static void tcp_recv_timestamp(struct msghdr *msg, const struct sock *sk,
struct scm_timestamping *tss) struct scm_timestamping *tss)
{ {
struct __kernel_old_timeval tv; int new_tstamp = sock_flag(sk, SOCK_TSTAMP_NEW);
bool has_timestamping = false; bool has_timestamping = false;
if (tss->ts[0].tv_sec || tss->ts[0].tv_nsec) { if (tss->ts[0].tv_sec || tss->ts[0].tv_nsec) {
if (sock_flag(sk, SOCK_RCVTSTAMP)) { if (sock_flag(sk, SOCK_RCVTSTAMP)) {
if (sock_flag(sk, SOCK_RCVTSTAMPNS)) { if (sock_flag(sk, SOCK_RCVTSTAMPNS)) {
put_cmsg(msg, SOL_SOCKET, SO_TIMESTAMPNS_OLD, if (new_tstamp) {
sizeof(tss->ts[0]), &tss->ts[0]); struct __kernel_timespec kts = {tss->ts[0].tv_sec, tss->ts[0].tv_nsec};
put_cmsg(msg, SOL_SOCKET, SO_TIMESTAMPNS_NEW,
sizeof(kts), &kts);
} else {
put_cmsg(msg, SOL_SOCKET, SO_TIMESTAMPNS_OLD,
sizeof(tss->ts[0]), &tss->ts[0]);
}
} else { } else {
tv.tv_sec = tss->ts[0].tv_sec; if (new_tstamp) {
tv.tv_usec = tss->ts[0].tv_nsec / 1000; struct __kernel_sock_timeval stv;
put_cmsg(msg, SOL_SOCKET, SO_TIMESTAMP_OLD, stv.tv_sec = tss->ts[0].tv_sec;
sizeof(tv), &tv); stv.tv_usec = tss->ts[0].tv_nsec / 1000;
put_cmsg(msg, SOL_SOCKET, SO_TIMESTAMP_NEW,
sizeof(stv), &stv);
} else {
struct __kernel_old_timeval tv;
tv.tv_sec = tss->ts[0].tv_sec;
tv.tv_usec = tss->ts[0].tv_nsec / 1000;
put_cmsg(msg, SOL_SOCKET, SO_TIMESTAMP_OLD,
sizeof(tv), &tv);
}
} }
} }
......
...@@ -348,7 +348,7 @@ static int rds_set_transport(struct rds_sock *rs, char __user *optval, ...@@ -348,7 +348,7 @@ static int rds_set_transport(struct rds_sock *rs, char __user *optval,
} }
static int rds_enable_recvtstamp(struct sock *sk, char __user *optval, static int rds_enable_recvtstamp(struct sock *sk, char __user *optval,
int optlen) int optlen, int optname)
{ {
int val, valbool; int val, valbool;
...@@ -360,6 +360,9 @@ static int rds_enable_recvtstamp(struct sock *sk, char __user *optval, ...@@ -360,6 +360,9 @@ static int rds_enable_recvtstamp(struct sock *sk, char __user *optval,
valbool = val ? 1 : 0; valbool = val ? 1 : 0;
if (optname == SO_TIMESTAMP_NEW)
sock_set_flag(sk, SOCK_TSTAMP_NEW);
if (valbool) if (valbool)
sock_set_flag(sk, SOCK_RCVTSTAMP); sock_set_flag(sk, SOCK_RCVTSTAMP);
else else
...@@ -431,8 +434,9 @@ static int rds_setsockopt(struct socket *sock, int level, int optname, ...@@ -431,8 +434,9 @@ static int rds_setsockopt(struct socket *sock, int level, int optname,
release_sock(sock->sk); release_sock(sock->sk);
break; break;
case SO_TIMESTAMP_OLD: case SO_TIMESTAMP_OLD:
case SO_TIMESTAMP_NEW:
lock_sock(sock->sk); lock_sock(sock->sk);
ret = rds_enable_recvtstamp(sock->sk, optval, optlen); ret = rds_enable_recvtstamp(sock->sk, optval, optlen, optname);
release_sock(sock->sk); release_sock(sock->sk);
break; break;
case SO_RDS_MSG_RXPATH_LATENCY: case SO_RDS_MSG_RXPATH_LATENCY:
......
...@@ -550,8 +550,20 @@ static int rds_cmsg_recv(struct rds_incoming *inc, struct msghdr *msg, ...@@ -550,8 +550,20 @@ static int rds_cmsg_recv(struct rds_incoming *inc, struct msghdr *msg,
if ((inc->i_rx_tstamp != 0) && if ((inc->i_rx_tstamp != 0) &&
sock_flag(rds_rs_to_sk(rs), SOCK_RCVTSTAMP)) { sock_flag(rds_rs_to_sk(rs), SOCK_RCVTSTAMP)) {
struct __kernel_old_timeval tv = ns_to_kernel_old_timeval(inc->i_rx_tstamp); struct __kernel_old_timeval tv = ns_to_kernel_old_timeval(inc->i_rx_tstamp);
ret = put_cmsg(msg, SOL_SOCKET, SO_TIMESTAMP_OLD,
sizeof(tv), &tv); if (!sock_flag(rds_rs_to_sk(rs), SOCK_TSTAMP_NEW)) {
ret = put_cmsg(msg, SOL_SOCKET, SO_TIMESTAMP_OLD,
sizeof(tv), &tv);
} else {
struct __kernel_sock_timeval sk_tv;
sk_tv.tv_sec = tv.tv_sec;
sk_tv.tv_usec = tv.tv_usec;
ret = put_cmsg(msg, SOL_SOCKET, SO_TIMESTAMP_NEW,
sizeof(sk_tv), &sk_tv);
}
if (ret) if (ret)
goto out; goto out;
} }
......
...@@ -705,6 +705,7 @@ void __sock_recv_timestamp(struct msghdr *msg, struct sock *sk, ...@@ -705,6 +705,7 @@ void __sock_recv_timestamp(struct msghdr *msg, struct sock *sk,
struct sk_buff *skb) struct sk_buff *skb)
{ {
int need_software_tstamp = sock_flag(sk, SOCK_RCVTSTAMP); int need_software_tstamp = sock_flag(sk, SOCK_RCVTSTAMP);
int new_tstamp = sock_flag(sk, SOCK_TSTAMP_NEW);
struct scm_timestamping tss; struct scm_timestamping tss;
int empty = 1, false_tstamp = 0; int empty = 1, false_tstamp = 0;
struct skb_shared_hwtstamps *shhwtstamps = struct skb_shared_hwtstamps *shhwtstamps =
...@@ -719,15 +720,33 @@ void __sock_recv_timestamp(struct msghdr *msg, struct sock *sk, ...@@ -719,15 +720,33 @@ void __sock_recv_timestamp(struct msghdr *msg, struct sock *sk,
if (need_software_tstamp) { if (need_software_tstamp) {
if (!sock_flag(sk, SOCK_RCVTSTAMPNS)) { if (!sock_flag(sk, SOCK_RCVTSTAMPNS)) {
struct __kernel_old_timeval tv; if (new_tstamp) {
skb_get_timestamp(skb, &tv); struct __kernel_sock_timeval tv;
put_cmsg(msg, SOL_SOCKET, SO_TIMESTAMP_OLD,
sizeof(tv), &tv); skb_get_new_timestamp(skb, &tv);
put_cmsg(msg, SOL_SOCKET, SO_TIMESTAMP_NEW,
sizeof(tv), &tv);
} else {
struct __kernel_old_timeval tv;
skb_get_timestamp(skb, &tv);
put_cmsg(msg, SOL_SOCKET, SO_TIMESTAMP_OLD,
sizeof(tv), &tv);
}
} else { } else {
struct timespec ts; if (new_tstamp) {
skb_get_timestampns(skb, &ts); struct __kernel_timespec ts;
put_cmsg(msg, SOL_SOCKET, SO_TIMESTAMPNS_OLD,
sizeof(ts), &ts); skb_get_new_timestampns(skb, &ts);
put_cmsg(msg, SOL_SOCKET, SO_TIMESTAMPNS_NEW,
sizeof(ts), &ts);
} else {
struct timespec ts;
skb_get_timestampns(skb, &ts);
put_cmsg(msg, SOL_SOCKET, SO_TIMESTAMPNS_OLD,
sizeof(ts), &ts);
}
} }
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment