Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
V
varnishevent3
Project
Project
Details
Activity
Releases
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
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
uplex-varnish
varnishevent3
Commits
7913f092
Commit
7913f092
authored
Jan 22, 2013
by
Geoff Simmons
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
varnishevent - added configuration
parent
36f25836
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
417 additions
and
36 deletions
+417
-36
Makefile.am
bin/varnishevent/Makefile.am
+1
-0
config.c
bin/varnishevent/config.c
+268
-0
data.c
bin/varnishevent/data.c
+44
-10
spscq.c
bin/varnishevent/spscq.c
+2
-3
varnishevent.c
bin/varnishevent/varnishevent.c
+15
-6
varnishevent.h
bin/varnishevent/varnishevent.h
+77
-12
writer.c
bin/varnishevent/writer.c
+10
-5
No files found.
bin/varnishevent/Makefile.am
View file @
7913f092
...
...
@@ -14,6 +14,7 @@ varnishevent_SOURCES = \
data.c
\
spscq.c
\
writer.c
\
config.c
\
$(top_builddir)
/lib/libvarnish/assert.c
\
$(top_builddir)
/lib/libvarnish/flopen.c
\
$(top_builddir)
/lib/libvarnish/version.c
\
...
...
bin/varnishevent/config.c
0 → 100644
View file @
7913f092
/*-
* Copyright (c) 2013 UPLEX Nils Goroll Systemoptimierung
* Copyright (c) 2013 Otto Gmbh & Co KG
* All rights reserved
* Use only with permission
*
* Author: Geoffrey Simmons <geoffrey.simmons@uplex.de>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
*/
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
#include <syslog.h>
#include <errno.h>
#include <stdlib.h>
#include <limits.h>
#include <math.h>
#include <unistd.h>
#include <pwd.h>
#include "varnishevent.h"
#include "libvarnish.h"
#define DEFAULT_USER "nobody"
static
const
int
facilitynum
[
8
]
=
{
LOG_LOCAL0
,
LOG_LOCAL1
,
LOG_LOCAL2
,
LOG_LOCAL3
,
LOG_LOCAL4
,
LOG_LOCAL5
,
LOG_LOCAL6
,
LOG_LOCAL7
};
static
int
conf_getFacility
(
const
char
*
facility
)
{
int
localnum
;
if
(
strcasecmp
(
facility
,
"USER"
)
==
0
)
return
LOG_USER
;
if
(
strlen
(
facility
)
!=
6
||
strncasecmp
(
facility
,
"LOCAL"
,
5
)
!=
0
||
!
isdigit
(
facility
[
5
]))
return
(
-
1
);
localnum
=
atoi
(
&
facility
[
5
]);
if
(
localnum
>
7
)
return
(
-
1
);
return
(
facilitynum
[
localnum
]);
}
static
int
conf_getUnsignedInt
(
const
char
*
rval
,
unsigned
*
i
)
{
long
n
;
char
*
p
;
errno
=
0
;
n
=
strtoul
(
rval
,
&
p
,
10
);
if
(
errno
)
return
(
errno
);
if
(
strlen
(
p
)
!=
0
)
return
(
EINVAL
);
if
(
n
<
0
||
n
>
UINT_MAX
)
return
(
ERANGE
);
*
i
=
(
unsigned
int
)
n
;
return
(
0
);
}
#define confString(name,fld) \
if (strcmp(lval, (name)) == 0) { \
strcpy((config.fld), rval); \
return(0); \
}
#define confUnsigned(name,fld) \
if (strcmp(lval, name) == 0) { \
unsigned int i; \
int err = conf_getUnsignedInt(rval, &i); \
if (err != 0) \
return err; \
config.fld = i; \
return(0); \
}
int
CONF_Add
(
const
char
*
lval
,
const
char
*
rval
)
{
int
ret
;
confString
(
"pid.file"
,
pid_file
);
confString
(
"varnish.name"
,
varnish_name
);
confString
(
"log.file"
,
log_file
);
confString
(
"varnish.bindump"
,
varnish_bindump
);
confUnsigned
(
"max.reclen"
,
max_reclen
);
confUnsigned
(
"max.headers"
,
max_headers
);
confUnsigned
(
"max.fd"
,
max_fd
);
confUnsigned
(
"max.data"
,
max_data
);
confUnsigned
(
"restarts"
,
restarts
);
confUnsigned
(
"monitor.interval"
,
monitor_interval
);
if
(
strcmp
(
lval
,
"syslog.facility"
)
==
0
)
{
if
((
ret
=
conf_getFacility
(
rval
))
<
0
)
return
EINVAL
;
config
.
syslog_facility
=
ret
;
strcpy
(
config
.
syslog_facility_name
,
rval
);
char
*
p
=
&
config
.
syslog_facility_name
[
0
];
do
{
*
p
=
toupper
(
*
p
);
}
while
(
*++
p
);
return
(
0
);
}
if
(
strcmp
(
lval
,
"user"
)
==
0
)
{
struct
passwd
*
pw
;
pw
=
getpwnam
(
rval
);
if
(
pw
==
NULL
)
return
(
EINVAL
);
strcpy
(
config
.
user_name
,
pw
->
pw_name
);
config
.
uid
=
pw
->
pw_uid
;
config
.
gid
=
pw
->
pw_gid
;
return
(
0
);
}
return
EINVAL
;
}
static
int
conf_ParseLine
(
char
*
ptr
,
char
**
lval
,
char
**
rval
)
{
char
*
endlval
;
*
lval
=
ptr
;
while
(
*++
ptr
&&
!
isspace
(
*
ptr
)
&&
*
ptr
!=
'='
)
;
if
(
*
ptr
==
'\0'
)
return
(
1
);
endlval
=
ptr
;
while
(
isspace
(
*
ptr
)
&&
*++
ptr
)
;
if
(
ptr
==
'\0'
||
*
ptr
!=
'='
)
return
(
1
);
while
(
*++
ptr
&&
isspace
(
*
ptr
))
;
if
(
ptr
==
'\0'
)
return
(
1
);
*
endlval
=
'\0'
;
*
rval
=
ptr
;
return
(
0
);
}
void
CONF_Init
(
void
)
{
struct
passwd
*
pw
;
strcpy
(
config
.
pid_file
,
DEFAULT_PID_FILE
);
config
.
varnish_name
[
0
]
=
'\0'
;
config
.
log_file
[
0
]
=
'\0'
;
config
.
varnish_bindump
[
0
]
=
'\0'
;
config
.
syslog_facility
=
LOG_LOCAL0
;
config
.
monitor_interval
=
30
;
config
.
max_reclen
=
DEFAULT_MAX_RECLEN
;
config
.
max_headers
=
DEFAULT_MAX_HEADERS
;
config
.
max_fd
=
DEFAULT_MAX_FD
;
config
.
max_data
=
DEFAULT_MAX_DATA
;
config
.
restarts
=
1
;
pw
=
getpwnam
(
DEFAULT_USER
);
if
(
pw
==
NULL
)
pw
=
getpwuid
(
getuid
());
AN
(
pw
);
strcpy
(
config
.
user_name
,
pw
->
pw_name
);
config
.
uid
=
pw
->
pw_uid
;
config
.
gid
=
pw
->
pw_gid
;
}
int
CONF_ReadFile
(
const
char
*
file
)
{
FILE
*
in
;
char
line
[
BUFSIZ
];
int
linenum
=
0
;
in
=
fopen
(
file
,
"r"
);
if
(
in
==
NULL
)
{
perror
(
file
);
return
(
-
1
);
}
while
(
fgets
(
line
,
BUFSIZ
,
in
)
!=
NULL
)
{
char
orig
[
BUFSIZ
];
linenum
++
;
char
*
comment
=
strchr
(
line
,
'#'
);
if
(
comment
!=
NULL
)
*
comment
=
'\0'
;
if
(
strlen
(
line
)
==
0
)
continue
;
char
*
ptr
=
line
+
strlen
(
line
)
-
1
;
while
(
ptr
!=
line
&&
isspace
(
*
ptr
))
--
ptr
;
ptr
[
isspace
(
*
ptr
)
?
0
:
1
]
=
'\0'
;
if
(
strlen
(
line
)
==
0
)
continue
;
ptr
=
line
;
while
(
isspace
(
*
ptr
)
&&
*++
ptr
)
;
strcpy
(
orig
,
ptr
);
char
*
lval
,
*
rval
;
if
(
conf_ParseLine
(
ptr
,
&
lval
,
&
rval
)
!=
0
)
{
fprintf
(
stderr
,
"Cannot parse %s line %d: '%s'
\n
"
,
file
,
linenum
,
orig
);
return
(
-
1
);
}
int
ret
;
if
((
ret
=
CONF_Add
((
const
char
*
)
lval
,
(
const
char
*
)
rval
))
!=
0
)
{
fprintf
(
stderr
,
"Error in %s line %d (%s): '%s'
\n
"
,
file
,
linenum
,
strerror
(
ret
),
orig
);
return
(
-
1
);
}
}
return
(
0
);
}
#define confdump(str,val) \
LOG_Log(LOG_DEBUG, "config: " str, (val))
#if 0
void
CONF_Dump(void)
{
confdump("pid.file = %s", config.pid_file);
confdump("varnish.name = %s", config.varnish_name);
confdump("log.file = %s",
strcmp(config.log_file,"-") == 0 ? "stdout" : config.log_file);
confdump("varnish.bindump = %s", config.varnish_bindump);
confdump("syslog.facility = %s", config.syslog_facility_name);
confdump("monitor.interval = %u", config.monitor_interval);
confdump("max.reclen = %u", config.max_reclen);
confdump("max.headers = %u", config.max_headers);
confdump("max.fd = %u", config.max_fd);
confdump("max.data = %u", config.max_data);
confdump("restarts = %u", config.restarts);
confdump("user = %s", config.user_name);
}
#endif
bin/varnishevent/data.c
View file @
7913f092
...
...
@@ -30,6 +30,7 @@
*/
#include <pthread.h>
#include <stdlib.h>
#include "varnishevent.h"
#include "vas.h"
...
...
@@ -41,6 +42,7 @@ static const char *statename[3] = { "EMPTY", "OPEN", "DONE" };
#endif
static
pthread_mutex_t
freelist_lock
;
static
char
*
bufptr
;
#if 0
static void
...
...
@@ -67,27 +69,59 @@ DATA_Clear_Logline(logline_t *ll)
ll
->
tag
[
i
].
len
=
0
;
}
#define INIT_LOG_RECORDS(fld) do { \
(fld).record = (record_t *) calloc(config.max_headers, sizeof(record_t)); \
if ((fld).record == NULL) \
return errno; \
for (int j = 0; j < config.max_headers; j++) { \
(fld).record[j].magic = RECORD_MAGIC; \
(fld).record[j].data = &bufptr[bufidx++ * config.max_reclen]; \
} \
} while (0)
int
DATA_Init
(
void
)
{
VSTAILQ_INIT
(
&
freehead
);
AZ
(
pthread_mutex_init
(
&
freelist_lock
,
&
attr_lock
));
global_nfree
=
0
;
int
bufidx
=
0
;
int
records
=
config
.
max_data
*
(
MAX_VSL_TAG
+
3
*
config
.
max_headers
);
bufptr
=
(
char
*
)
calloc
(
records
,
config
.
max_reclen
);
if
(
bufptr
==
NULL
)
return
errno
;
logline
=
(
logline_t
*
)
calloc
(
config
.
max_data
,
sizeof
(
logline_t
));
if
(
logline
==
NULL
)
return
errno
;
fd_tbl
=
(
fd_t
*
)
calloc
(
config
.
max_fd
,
sizeof
(
fd_t
));
if
(
fd_tbl
==
NULL
)
return
errno
;
/*
* XXX: allocate these tables from configured sizes
*/
for
(
int
i
=
0
;
i
<
MAX_DATA
;
i
++
)
{
VSTAILQ_INIT
(
&
freehead
);
for
(
int
i
=
0
;
i
<
config
.
max_data
;
i
++
)
{
INIT_LOG_RECORDS
(
logline
[
i
].
rx_headers
);
INIT_LOG_RECORDS
(
logline
[
i
].
tx_headers
);
INIT_LOG_RECORDS
(
logline
[
i
].
vcl_log
);
for
(
int
j
=
0
;
j
<
MAX_VSL_TAG
;
j
++
)
{
logline
[
i
].
tag
[
j
].
magic
=
RECORD_MAGIC
;
logline
[
i
].
tag
[
j
].
data
=
&
bufptr
[
bufidx
++
*
config
.
max_reclen
];
}
logline
[
i
].
magic
=
LOGLINE_MAGIC
;
DATA_Clear_Logline
(
&
logline
[
i
]);
VSTAILQ_INSERT_TAIL
(
&
freehead
,
&
logline
[
i
],
freelist
);
}
for
(
int
i
=
0
;
i
<
MAX_FD
;
i
++
)
{
fd_tbl
[
i
].
ll
=
NULL
;
fd_tbl
[
i
].
state
=
FD_EMPTY
;
for
(
int
k
=
0
;
k
<
config
.
max_fd
;
k
++
)
{
fd_tbl
[
k
].
ll
=
NULL
;
fd_tbl
[
k
].
state
=
FD_EMPTY
;
}
AZ
(
pthread_mutex_init
(
&
freelist_lock
,
&
attr_lock
));
global_nfree
=
0
;
return
(
0
);
}
...
...
bin/varnishevent/spscq.c
View file @
7913f092
...
...
@@ -45,9 +45,8 @@ unsigned SPSCQ_Len(void) {
void
SPSCQ_Enq
(
logline_t
*
ptr
)
{
/* XXX: compare to a configured max in the assert
XXX: is the assertion accurate if enqs & deqs are not synced? */
assert
(
enqs
-
deqs
<
MAX_DATA
);
/* XXX: is the assertion accurate if enqs & deqs are not synced? */
assert
(
enqs
-
deqs
<
config
.
max_data
);
enqs
++
;
VSTAILQ_INSERT_TAIL
(
&
spscq_head
,
ptr
,
spscq
);
}
...
...
bin/varnishevent/varnishevent.c
View file @
7913f092
...
...
@@ -160,9 +160,9 @@ collect(struct logline_t *lp, enum VSL_tag_e tag, unsigned spec,
assert
(
lp
->
spec
==
spec
);
}
/* XXX:
use a configured maximum, and
issue a warning when too long */
if
(
len
>
MAX_RECLEN
)
len
=
MAX_RECLEN
;
/* XXX: issue a warning when too long */
if
(
len
>
config
.
max_reclen
)
len
=
config
.
max_reclen
;
/* XXX: check against overflow for lp->n* */
if
(
tag
==
SLT_RxHeader
)
{
...
...
@@ -307,7 +307,7 @@ usage(void)
int
main
(
int
argc
,
char
*
argv
[])
{
int
c
;
int
c
,
errnum
;
int
a_flag
=
0
,
D_flag
=
0
,
format_flag
=
0
;
const
char
*
P_arg
=
NULL
;
const
char
*
w_arg
=
NULL
;
...
...
@@ -317,6 +317,7 @@ main(int argc, char *argv[])
vd
=
VSM_New
();
VSL_Setup
(
vd
);
CONF_Init
();
while
((
c
=
getopt
(
argc
,
argv
,
VSL_ARGS
"aDP:Vw:fF:"
))
!=
-
1
)
{
switch
(
c
)
{
...
...
@@ -407,9 +408,17 @@ main(int argc, char *argv[])
AZ
(
pthread_mutexattr_setpshared
(
&
attr_lock
,
PTHREAD_PROCESS_PRIVATE
));
AZ
(
pthread_condattr_setpshared
(
&
attr_cond
,
PTHREAD_PROCESS_PRIVATE
));
WRT_Init
(
format
,
of
);
DATA_Init
();
/* XXX: log errors */
if
((
errnum
=
DATA_Init
())
!=
0
)
{
fprintf
(
stderr
,
"Cannot init data structures: %s
\n
"
,
strerror
(
errnum
));
exit
(
1
);
}
if
((
errnum
=
WRT_Init
(
format
,
of
))
!=
0
)
{
fprintf
(
stderr
,
"Cannot init writer thread: %s
\n
"
,
strerror
(
errnum
));
exit
(
1
);
}
WRT_Start
();
while
(
!
WRT_Running
())
;
...
...
bin/varnishevent/varnishevent.h
View file @
7913f092
...
...
@@ -32,14 +32,17 @@
#include <stdint.h>
#include <stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include "vqueue.h"
/* XXX: configure */
#define MAX_RECLEN 255
#define MAX_HEADERS 64
#define MAX_FD 1024
#define MAX_DATA 3072
/* Defaults from Varnish 3.0.3 */
#define DEFAULT_MAX_RECLEN 255
/* shm_reclen */
#define DEFAULT_MAX_HEADERS 64
/* http_max_hdr */
#define DEFAULT_MAX_FD 1024
#define DEFAULT_MAX_DATA 3072
#define DEFAULT_PID_FILE "/var/run/varnishevent.pid"
#define MAX_VSL_TAG 256
...
...
@@ -50,14 +53,14 @@ typedef enum {
}
data_state_e
;
typedef
struct
{
/* XXX: allocate dynamically from configurable max_reclen */
char
data
[
MAX_RECLEN
];
unsigned
magic
;
#define RECORD_MAGIC 0xdf4399b1
char
*
data
;
unsigned
len
;
}
record_t
;
typedef
struct
{
/* XXX: allocate dynamically from configurable max_headers */
record_t
record
[
MAX_HEADERS
];
record_t
*
record
;
unsigned
nrec
;
}
hdr_t
;
...
...
@@ -75,17 +78,19 @@ typedef struct logline_t {
VSTAILQ_ENTRY
(
logline_t
)
spscq
;
}
logline_t
;
logline_t
logline
[
MAX_DATA
]
;
logline_t
*
logline
;
typedef
enum
{
FD_EMPTY
=
0
,
FD_OPEN
}
fd_state_e
;
struc
t
{
typedef
struct
fd_
t
{
logline_t
*
ll
;
fd_state_e
state
;
}
fd_tbl
[
MAX_FD
];
}
fd_t
;
fd_t
*
fd_tbl
;
VSTAILQ_HEAD
(
freehead_s
,
logline_t
);
...
...
@@ -106,9 +111,69 @@ pthread_mutex_t spscq_ready_lock;
pthread_mutexattr_t
attr_lock
;
pthread_condattr_t
attr_cond
;
struct
config
{
char
pid_file
[
BUFSIZ
];
/* VSL 'n' argument */
char
varnish_name
[
BUFSIZ
];
char
log_file
[
BUFSIZ
];
/* VSL 'r' argument */
char
varnish_bindump
[
BUFSIZ
];
int
syslog_facility
;
char
syslog_facility_name
[
BUFSIZ
];
unsigned
monitor_interval
;
/* varnishd param shm_reclen */
unsigned
max_reclen
;
/* varnishd param http_max_hdr */
unsigned
max_headers
;
unsigned
max_fd
;
unsigned
max_data
;
unsigned
restarts
;
char
user_name
[
BUFSIZ
];
uid_t
uid
;
gid_t
gid
;
}
config
;
/* varnishevent.c */
int
RDR_Waiting
(
void
);
/* config.c */
void
CONF_Init
(
void
);
int
CONF_Add
(
const
char
*
lval
,
const
char
*
rval
);
int
CONF_ReadFile
(
const
char
*
file
);
#if 0
void CONF_Dump(void);
#endif
#if 0
/* log.c */
typedef void log_log_t(int level, const char *msg, ...);
typedef void log_setlevel_t(int level);
typedef void log_close_t(void);
struct logconf {
log_log_t *log;
log_setlevel_t *setlevel;
log_close_t *close;
FILE *out;
int level;
} logconf;
int LOG_Open(const char *progname);
/* XXX: __VA_ARGS__ can't be empty ... */
#define LOG_Log0(level, msg) logconf.log(level, msg)
#define LOG_Log(level, msg, ...) logconf.log(level, msg, __VA_ARGS__)
#define LOG_SetLevel(level) logconf.setlevel(level)
#define LOG_Close() logconf.close()
#endif
/* data.c */
int
DATA_Init
(
void
);
void
DATA_Clear_Logline
(
logline_t
*
ll
);
...
...
bin/varnishevent/writer.c
View file @
7913f092
...
...
@@ -43,7 +43,6 @@
#include "miniobj.h"
#include "vsb.h"
/* XXX: logging */
#define LOG_Log0(l,s) fprintf(stderr, (s))
#define LOG_Log(level, msg, ...) vfprintf(stderr, msg, __VA_ARGS__)
...
...
@@ -86,7 +85,6 @@ static const char *cformat;
static
const
char
*
bformat
=
"%t dir=%d url=[%U] rc=[%s] len=[%b] xid=[%{X-Varnish}i] fetch_body=[%{tag:Fetch_Body}x]"
;
static
struct
vsb
*
os
;
static
FILE
*
fo
;
...
...
@@ -109,6 +107,8 @@ static writer_data_t wrt_data;
static
unsigned
run
,
cleaned
=
0
;
static
char
*
scratch
;
static
inline
void
wrt_return_freelist
(
void
)
{
...
...
@@ -136,11 +136,12 @@ get_hdr(const char *hdr, hdr_t *tbl)
static
inline
char
*
get_fld
(
record_t
*
rec
,
int
n
)
{
char
*
ret
=
NULL
,
*
s
,
str
[
MAX_RECLEN
]
;
char
*
ret
=
NULL
,
*
s
;
int
i
=
0
;
strncpy
(
str
,
rec
->
data
,
rec
->
len
);
s
=
str
;
AN
(
scratch
);
strncpy
(
scratch
,
rec
->
data
,
rec
->
len
);
s
=
scratch
;
do
{
ret
=
strtok
(
s
,
"
\t
"
);
s
=
NULL
;
...
...
@@ -793,6 +794,10 @@ static void wrt_cleanup(void)
int
WRT_Init
(
const
char
*
format
,
FILE
*
out
)
{
scratch
=
(
char
*
)
malloc
(
config
.
max_reclen
);
if
(
scratch
==
NULL
)
return
errno
;
TIM_real_mono_diff
=
TIM_real
()
-
TIM_mono
();
run
=
1
;
...
...
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