Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
V
varnishevent
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
1
Issues
1
List
Board
Labels
Milestones
Merge Requests
1
Merge Requests
1
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
varnishevent
Commits
dbc71f01
Commit
dbc71f01
authored
Apr 18, 2015
by
Geoff Simmons
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
implement the -P option (writing a pid file)
parent
f2d8d93b
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
495 additions
and
29 deletions
+495
-29
Makefile.am
src/Makefile.am
+5
-1
flopen.c
src/flopen.c
+133
-0
flopen.h
src/flopen.h
+37
-0
varnishevent.c
src/varnishevent.c
+23
-28
vpf.c
src/vpf.c
+257
-0
vpf.h
src/vpf.h
+40
-0
No files found.
src/Makefile.am
View file @
dbc71f01
...
...
@@ -26,7 +26,11 @@ varnishevent_SOURCES = \
format.c
\
handler.c
\
vtim.h
\
vtim.c
vtim.c
\
vpf.h
\
vpf.c
\
flopen.h
\
flopen.c
varnishevent_LDADD
=
\
${
PTHREAD_LIBS
}
${
RT_LIBS
}
${
LIBM
}
@VARNISH_LIBS@
\
...
...
src/flopen.c
0 → 100644
View file @
dbc71f01
/*-
* Copyright (c) 2007 Dag-Erling Coïdan Smørgrav
* All rights reserved.
*
* 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
* in this position and unchanged.
* 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 THE 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.
* Derived from:
* $FreeBSD: head/lib/libutil/flopen.c 184094 2008-10-20 18:11:30Z des $
*/
#include "config.h"
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include "flopen.h"
int
flopen
(
const
char
*
path
,
int
flags
,
...)
{
int
fd
,
operation
,
serrno
,
trunc
;
struct
flock
lock
;
struct
stat
sb
,
fsb
;
mode_t
mode
;
#ifdef O_EXLOCK
flags
&=
~
O_EXLOCK
;
#endif
mode
=
0
;
if
(
flags
&
O_CREAT
)
{
va_list
ap
;
va_start
(
ap
,
flags
);
mode
=
(
mode_t
)
va_arg
(
ap
,
int
);
/* mode_t promoted to int */
va_end
(
ap
);
}
memset
(
&
lock
,
0
,
sizeof
lock
);
lock
.
l_type
=
((
flags
&
O_ACCMODE
)
==
O_RDONLY
)
?
F_RDLCK
:
F_WRLCK
;
lock
.
l_whence
=
SEEK_SET
;
operation
=
(
flags
&
O_NONBLOCK
)
?
F_SETLK
:
F_SETLKW
;
trunc
=
(
flags
&
O_TRUNC
);
flags
&=
~
O_TRUNC
;
for
(;;)
{
if
((
fd
=
open
(
path
,
flags
,
mode
))
==
-
1
)
/* non-existent or no access */
return
(
-
1
);
if
(
fcntl
(
fd
,
operation
,
&
lock
)
==
-
1
)
{
/* unsupported or interrupted */
serrno
=
errno
;
(
void
)
close
(
fd
);
errno
=
serrno
;
return
(
-
1
);
}
if
(
stat
(
path
,
&
sb
)
==
-
1
)
{
/* disappeared from under our feet */
(
void
)
close
(
fd
);
continue
;
}
if
(
fstat
(
fd
,
&
fsb
)
==
-
1
)
{
/* can't happen [tm] */
serrno
=
errno
;
(
void
)
close
(
fd
);
errno
=
serrno
;
return
(
-
1
);
}
if
(
sb
.
st_dev
!=
fsb
.
st_dev
||
sb
.
st_ino
!=
fsb
.
st_ino
)
{
/* changed under our feet */
(
void
)
close
(
fd
);
continue
;
}
if
(
trunc
&&
ftruncate
(
fd
,
0
)
!=
0
)
{
/* can't happen [tm] */
serrno
=
errno
;
(
void
)
close
(
fd
);
errno
=
serrno
;
return
(
-
1
);
}
return
(
fd
);
}
}
/* Tests if the given fd is locked through flopen
* If pid is non-NULL, stores the pid of the process holding the lock there
* Returns 1 if the file is locked
* Returns 0 if the file is unlocked
* Returns -1 on error (and errno)
*/
int
fltest
(
int
fd
,
pid_t
*
pid
)
{
struct
flock
lock
;
memset
(
&
lock
,
0
,
sizeof
lock
);
lock
.
l_type
=
F_WRLCK
;
lock
.
l_whence
=
SEEK_SET
;
if
(
fcntl
(
fd
,
F_GETLK
,
&
lock
)
==
-
1
)
return
(
-
1
);
if
(
lock
.
l_type
==
F_UNLCK
)
return
(
0
);
if
(
pid
!=
NULL
)
*
pid
=
lock
.
l_pid
;
return
(
1
);
}
src/flopen.h
0 → 100644
View file @
dbc71f01
/*-
* Copyright (c) 2007 Dag-Erling Coïdan Smørgrav
* All rights reserved.
*
* 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
* in this position and unchanged.
* 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 THE 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.
*
* Derived from:
* $FreeBSD: src/lib/libutil/libutil.h,v 1.44 2007/05/10 15:01:42 des Exp $
*/
#ifndef FLOPEN_H_INCLUDED
#define FLOPEN_H_INCLUDED
int
flopen
(
const
char
*
,
int
,
...);
int
fltest
(
int
fd
,
pid_t
*
pid
);
#endif
src/varnishevent.c
View file @
dbc71f01
...
...
@@ -60,11 +60,7 @@
#include <errno.h>
#include "vsb.h"
#if 0
#include "vpf.h"
#endif
#include "vqueue.h"
#include "vapi/vsl.h"
#include "vre.h"
#include "miniobj.h"
...
...
@@ -73,6 +69,7 @@
#include "varnishevent.h"
#include "vtim.h"
#include "vpf.h"
#define DEFAULT_CONFIG "/etc/varnishevent.conf"
...
...
@@ -416,14 +413,9 @@ int
main
(
int
argc
,
char
*
argv
[])
{
int
c
,
errnum
,
status
,
a_flag
=
0
,
v_flag
=
0
,
d_flag
=
0
,
D_flag
=
0
;
#if 0
const char *P_arg = NULL;
#endif
char
*
w_arg
=
NULL
,
*
q_arg
=
NULL
,
*
g_arg
=
NULL
;
char
scratch
[
BUFSIZ
];
#if 0
char
*
P_arg
=
NULL
,
*
w_arg
=
NULL
,
*
q_arg
=
NULL
,
*
g_arg
=
NULL
,
scratch
[
BUFSIZ
];
struct
vpf_fh
*
pfh
=
NULL
;
#endif
struct
VSL_data
*
vsl
;
struct
VSLQ
*
vslq
;
struct
VSM_data
*
vsm
;
...
...
@@ -454,16 +446,14 @@ main(int argc, char *argv[])
exit
(
EXIT_FAILURE
);
#endif
break
;
#if 0
case
'P'
:
P_arg = optarg
;
REPLACE
(
P_arg
,
optarg
)
;
break
;
#endif
case
'V'
:
VCS_Message
(
"varnishevent"
);
exit
(
0
);
case
'w'
:
w_arg
=
optarg
;
REPLACE
(
w_arg
,
optarg
)
;
break
;
case
'v'
:
v_flag
=
1
;
...
...
@@ -501,21 +491,15 @@ main(int argc, char *argv[])
}
}
#if 0
if
(
P_arg
&&
(
pfh
=
VPF_Open
(
P_arg
,
0644
,
NULL
))
==
NULL
)
{
perror
(
P_arg
);
exit(
1
);
exit
(
EXIT_FAILURE
);
}
#endif
#ifdef HAVE_DAEMON
if
(
D_flag
&&
daemon
(
0
,
0
)
==
-
1
)
{
perror
(
"daemon()"
);
#if 0
if (pfh != NULL)
VPF_Remove(pfh);
#endif
exit
(
1
);
exit
(
EXIT_FAILURE
);
}
#endif
...
...
@@ -529,6 +513,16 @@ main(int argc, char *argv[])
LOG_Log
(
LOG_INFO
,
"initializing (%s)"
,
VCS_version
);
if
(
pfh
!=
NULL
)
{
errno
=
0
;
if
(
VPF_Write
(
pfh
)
!=
0
)
{
LOG_Log
(
LOG_CRIT
,
"Cannot write pid file %s, exiting: %s"
,
P_arg
,
strerror
(
errno
));
exit
(
EXIT_FAILURE
);
}
errno
=
0
;
}
/* XXX: also set grouping in config file */
if
(
g_arg
!=
NULL
)
{
grouping
=
VSLQ_Name2Grouping
(
g_arg
,
-
1
);
...
...
@@ -613,11 +607,6 @@ main(int argc, char *argv[])
/* Install signal handlers */
#include "signals.h"
#if 0
if (pfh != NULL)
VPF_Write(pfh);
#endif
if
(
w_arg
)
strcpy
(
config
.
output_file
,
w_arg
);
if
(
!
EMPTY
(
config
.
output_file
))
...
...
@@ -769,6 +758,12 @@ main(int argc, char *argv[])
FMT_Fini
();
AZ
(
pthread_cond_destroy
(
&
spscq_ready_cond
));
AZ
(
pthread_mutex_destroy
(
&
spscq_ready_lock
));
if
(
pfh
!=
NULL
)
{
errno
=
0
;
if
(
VPF_Remove
(
pfh
)
!=
0
)
LOG_Log
(
LOG_WARNING
,
"Could not remove pid file %s: %s"
,
P_arg
,
strerror
(
errno
));
}
LOG_Log0
(
LOG_INFO
,
"Exiting"
);
LOG_Close
();
...
...
src/vpf.c
0 → 100644
View file @
dbc71f01
/*-
* Copyright (c) 2005 Pawel Jakub Dawidek <pjd@FreeBSD.org>
* All rights reserved.
*
* 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 AUTHORS 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 THE AUTHORS 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.
* Derived from:
* $FreeBSD: head/lib/libutil/pidfile.c 184091 2008-10-20 17:41:08Z des $
*/
#include "config.h"
#include <sys/param.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "flopen.h"
#include "vas.h"
#include "vpf.h"
struct
vpf_fh
{
int
pf_fd
;
char
pf_path
[
MAXPATHLEN
+
1
];
dev_t
pf_dev
;
ino_t
pf_ino
;
};
static
int
_VPF_Remove
(
struct
vpf_fh
*
pfh
,
int
freeit
);
static
int
vpf_verify
(
const
struct
vpf_fh
*
pfh
)
{
struct
stat
sb
;
if
(
pfh
==
NULL
||
pfh
->
pf_fd
==
-
1
)
return
(
EINVAL
);
/*
* Check remembered descriptor.
*/
if
(
fstat
(
pfh
->
pf_fd
,
&
sb
)
==
-
1
)
return
(
errno
);
if
(
sb
.
st_dev
!=
pfh
->
pf_dev
||
sb
.
st_ino
!=
pfh
->
pf_ino
)
return
(
EINVAL
);
return
(
0
);
}
static
int
vpf_read
(
const
char
*
path
,
pid_t
*
pidptr
)
{
char
buf
[
16
],
*
endptr
;
int
error
,
fd
,
i
;
fd
=
open
(
path
,
O_RDONLY
);
if
(
fd
==
-
1
)
return
(
errno
);
i
=
read
(
fd
,
buf
,
sizeof
(
buf
)
-
1
);
error
=
errno
;
/* Remember errno in case close() wants to change it. */
(
void
)
close
(
fd
);
if
(
i
==
-
1
)
return
(
error
);
buf
[
i
]
=
'\0'
;
*
pidptr
=
strtol
(
buf
,
&
endptr
,
10
);
if
(
endptr
!=
&
buf
[
i
])
return
(
EINVAL
);
return
(
0
);
}
struct
vpf_fh
*
VPF_Open
(
const
char
*
path
,
mode_t
mode
,
pid_t
*
pidptr
)
{
struct
vpf_fh
*
pfh
;
struct
stat
sb
;
int
error
,
fd
,
len
;
pfh
=
malloc
(
sizeof
(
*
pfh
));
if
(
pfh
==
NULL
)
return
(
NULL
);
#if 0
if (path == NULL)
len = snprintf(pfh->pf_path, sizeof(pfh->pf_path),
"/var/run/%s.pid", getprogname());
else
#endif
{
assert
(
path
!=
NULL
);
len
=
snprintf
(
pfh
->
pf_path
,
sizeof
(
pfh
->
pf_path
),
"%s"
,
path
);
}
if
(
len
>=
(
int
)
sizeof
(
pfh
->
pf_path
))
{
free
(
pfh
);
errno
=
ENAMETOOLONG
;
return
(
NULL
);
}
/*
* Open the PID file and obtain exclusive lock.
* We truncate PID file here only to remove old PID immediatelly,
* PID file will be truncated again in VPF_Write(), so
* VPF_Write() can be called multiple times.
*/
fd
=
flopen
(
pfh
->
pf_path
,
O_WRONLY
|
O_CREAT
|
O_TRUNC
|
O_NONBLOCK
,
mode
);
if
(
fd
==
-
1
)
{
if
(
errno
==
EWOULDBLOCK
&&
pidptr
!=
NULL
)
{
errno
=
vpf_read
(
pfh
->
pf_path
,
pidptr
);
if
(
errno
==
0
)
errno
=
EEXIST
;
}
free
(
pfh
);
return
(
NULL
);
}
/*
* Remember file information, so in VPF_Write() we are sure we write
* to the proper descriptor.
*/
if
(
fstat
(
fd
,
&
sb
)
==
-
1
)
{
error
=
errno
;
(
void
)
unlink
(
pfh
->
pf_path
);
(
void
)
close
(
fd
);
free
(
pfh
);
errno
=
error
;
return
(
NULL
);
}
pfh
->
pf_fd
=
fd
;
pfh
->
pf_dev
=
sb
.
st_dev
;
pfh
->
pf_ino
=
sb
.
st_ino
;
return
(
pfh
);
}
int
VPF_Write
(
struct
vpf_fh
*
pfh
)
{
char
pidstr
[
16
];
int
error
,
fd
;
/*
* Check remembered descriptor, so we don't overwrite some other
* file if pidfile was closed and descriptor reused.
*/
errno
=
vpf_verify
(
pfh
);
if
(
errno
!=
0
)
{
/*
* Don't close descriptor, because we are not sure if it's ours.
*/
return
(
-
1
);
}
fd
=
pfh
->
pf_fd
;
/*
* Truncate PID file, so multiple calls of VPF_Write() are allowed.
*/
if
(
ftruncate
(
fd
,
0
)
==
-
1
)
{
error
=
errno
;
(
void
)
_VPF_Remove
(
pfh
,
0
);
errno
=
error
;
return
(
-
1
);
}
error
=
snprintf
(
pidstr
,
sizeof
(
pidstr
),
"%ju"
,
(
uintmax_t
)
getpid
());
assert
(
error
<
sizeof
pidstr
);
if
(
pwrite
(
fd
,
pidstr
,
strlen
(
pidstr
),
0
)
!=
(
ssize_t
)
strlen
(
pidstr
))
{
error
=
errno
;
(
void
)
_VPF_Remove
(
pfh
,
0
);
errno
=
error
;
return
(
-
1
);
}
return
(
0
);
}
int
VPF_Close
(
struct
vpf_fh
*
pfh
)
{
int
error
;
error
=
vpf_verify
(
pfh
);
if
(
error
!=
0
)
{
errno
=
error
;
return
(
-
1
);
}
if
(
close
(
pfh
->
pf_fd
)
==
-
1
)
error
=
errno
;
free
(
pfh
);
if
(
error
!=
0
)
{
errno
=
error
;
return
(
-
1
);
}
return
(
0
);
}
static
int
_VPF_Remove
(
struct
vpf_fh
*
pfh
,
int
freeit
)
{
int
error
;
error
=
vpf_verify
(
pfh
);
if
(
error
!=
0
)
{
errno
=
error
;
return
(
-
1
);
}
if
(
unlink
(
pfh
->
pf_path
)
==
-
1
)
error
=
errno
;
if
(
close
(
pfh
->
pf_fd
)
==
-
1
)
{
if
(
error
==
0
)
error
=
errno
;
}
if
(
freeit
)
free
(
pfh
);
else
pfh
->
pf_fd
=
-
1
;
if
(
error
!=
0
)
{
errno
=
error
;
return
(
-
1
);
}
return
(
0
);
}
int
VPF_Remove
(
struct
vpf_fh
*
pfh
)
{
return
(
_VPF_Remove
(
pfh
,
1
));
}
src/vpf.h
0 → 100644
View file @
dbc71f01
/*-
* Copyright (c) 2005 Pawel Jakub Dawidek <pjd@FreeBSD.org>
* All rights reserved.
*
* 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 AUTHORS 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 THE AUTHORS 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.
*
* Derived from:
* $FreeBSD: src/lib/libutil/libutil.h,v 1.41 2005/08/24 17:21:38 pjd Exp $
*/
#ifndef VPF_H_INCLUDED
#define VPF_H_INCLUDED
struct
vpf_fh
;
struct
vpf_fh
*
VPF_Open
(
const
char
*
path
,
mode_t
mode
,
pid_t
*
pidptr
);
int
VPF_Write
(
struct
vpf_fh
*
pfh
);
int
VPF_Close
(
struct
vpf_fh
*
pfh
);
int
VPF_Remove
(
struct
vpf_fh
*
pfh
);
#endif
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