Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
M
mariadb
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
mariadb
Commits
122329f7
Commit
122329f7
authored
Sep 05, 2001
by
sasha@mysql.sashanet.com
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
more work on mysqlmanager
parent
595762d0
Changes
1
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
285 additions
and
31 deletions
+285
-31
tools/mysqlmanager.c
tools/mysqlmanager.c
+285
-31
No files found.
tools/mysqlmanager.c
View file @
122329f7
...
@@ -40,28 +40,28 @@
...
@@ -40,28 +40,28 @@
#include <violite.h>
#include <violite.h>
#include <my_pthread.h>
#include <my_pthread.h>
#define M
NGD
_VERSION "1.0"
#define M
ANAGER
_VERSION "1.0"
#define M
NGD
_GREETING "MySQL Server Management Daemon v.1.0"
#define M
ANAGER
_GREETING "MySQL Server Management Daemon v.1.0"
#define LOG_ERR 1
#define LOG_ERR 1
#define LOG_WARN 2
#define LOG_WARN 2
#define LOG_INFO 3
#define LOG_INFO 3
#define LOG_DEBUG 4
#define LOG_DEBUG 4
#ifndef M
NGD
_PORT
#ifndef M
ANAGER
_PORT
#define M
NGD
_PORT 23546
#define M
ANAGER
_PORT 23546
#endif
#endif
#ifndef M
NGD
_MAX_CMD_LEN
#ifndef M
ANAGER
_MAX_CMD_LEN
#define M
NGD
_MAX_CMD_LEN 16384
#define M
ANAGER
_MAX_CMD_LEN 16384
#endif
#endif
#ifndef M
NGD
_LOG_FILE
#ifndef M
ANAGER
_LOG_FILE
#define M
NGD
_LOG_FILE "/var/log/mysqlmanager.log"
#define M
ANAGER
_LOG_FILE "/var/log/mysqlmanager.log"
#endif
#endif
#ifndef M
NGD
_BACK_LOG
#ifndef M
ANAGER
_BACK_LOG
#define M
NGD
_BACK_LOG 50
#define M
ANAGER
_BACK_LOG 50
#endif
#endif
#ifndef MAX_USER_NAME
#ifndef MAX_USER_NAME
...
@@ -73,17 +73,18 @@
...
@@ -73,17 +73,18 @@
set by the user
set by the user
*/
*/
uint
manager_port
=
M
NGD
_PORT
;
uint
manager_port
=
M
ANAGER
_PORT
;
FILE
*
errfp
;
FILE
*
errfp
;
const
char
*
manager_log_file
=
M
NGD
_LOG_FILE
;
const
char
*
manager_log_file
=
M
ANAGER
_LOG_FILE
;
pthread_mutex_t
lock_log
,
lock_shutdown
;
pthread_mutex_t
lock_log
,
lock_shutdown
,
lock_exec_hash
;
int
manager_sock
=
-
1
;
int
manager_sock
=
-
1
;
struct
sockaddr_in
manager_addr
;
struct
sockaddr_in
manager_addr
;
ulong
manager_bind_addr
=
INADDR_ANY
;
ulong
manager_bind_addr
=
INADDR_ANY
;
int
manager_back_log
=
M
NGD
_BACK_LOG
;
int
manager_back_log
=
M
ANAGER
_BACK_LOG
;
int
in_shutdown
=
0
,
shutdown_requested
=
0
;
int
in_shutdown
=
0
,
shutdown_requested
=
0
;
const
char
*
manager_greeting
=
MNGD_GREETING
;
const
char
*
manager_greeting
=
MANAGER_GREETING
;
uint
manager_max_cmd_len
=
MNGD_MAX_CMD_LEN
;
uint
manager_max_cmd_len
=
MANAGER_MAX_CMD_LEN
;
int
one_thread
=
0
;
/* for debugging */
/* messages */
/* messages */
...
@@ -98,7 +99,6 @@ uint manager_max_cmd_len = MNGD_MAX_CMD_LEN;
...
@@ -98,7 +99,6 @@ uint manager_max_cmd_len = MNGD_MAX_CMD_LEN;
#define MSG_CLIENT_ERR 450
#define MSG_CLIENT_ERR 450
#define MSG_INTERNAL_ERR 500
#define MSG_INTERNAL_ERR 500
/* access flags */
/* access flags */
#define PRIV_SHUTDOWN 1
#define PRIV_SHUTDOWN 1
...
@@ -112,9 +112,18 @@ struct manager_thd
...
@@ -112,9 +112,18 @@ struct manager_thd
int
fatal
,
finished
;
int
fatal
,
finished
;
};
};
struct
manager_thd
*
manager_thd_new
(
Vio
*
vio
);
HASH
exec_hash
;
void
manager_thd_free
(
struct
manager_thd
*
thd
);
static
struct
manager_thd
*
manager_thd_new
(
Vio
*
vio
);
static
struct
manager_exec
*
manager_exec_new
(
char
*
arg_start
,
char
*
arg_end
);
static
void
manager_exec_print
(
Vio
*
vio
,
struct
manager_exec
*
e
);
static
void
manager_thd_free
(
struct
manager_thd
*
thd
);
static
void
manager_exec_free
(
void
*
e
);
static
char
*
arg_strmov
(
char
*
dest
,
const
char
*
src
,
int
n
);
static
byte
*
get_exec_key
(
const
byte
*
e
,
uint
*
len
,
my_bool
__attribute__
((
unused
))
t
);
static
uint
tokenize_args
(
char
*
arg_start
,
char
**
arg_end
);
static
void
init_arg_array
(
char
*
arg_str
,
char
**
args
,
uint
arg_count
);
typedef
int
(
*
manager_cmd_handler
)(
struct
manager_thd
*
,
char
*
,
char
*
);
typedef
int
(
*
manager_cmd_handler
)(
struct
manager_thd
*
,
char
*
,
char
*
);
...
@@ -126,6 +135,20 @@ struct manager_cmd
...
@@ -126,6 +135,20 @@ struct manager_cmd
int
len
;
int
len
;
};
};
struct
manager_exec
{
char
*
ident
;
int
ident_len
;
const
char
*
error
;
char
*
bin_path
;
char
**
args
;
char
con_user
[
16
];
char
con_pass
[
16
];
int
con_port
;
char
con_sock
[
FN_REFLEN
];
char
*
data_buf
;
};
#define HANDLE_DECL(com) static int handle_ ## com (struct manager_thd* thd,\
#define HANDLE_DECL(com) static int handle_ ## com (struct manager_thd* thd,\
char* args_start,char* args_end)
char* args_start,char* args_end)
...
@@ -138,12 +161,16 @@ HANDLE_NOARG_DECL(ping);
...
@@ -138,12 +161,16 @@ HANDLE_NOARG_DECL(ping);
HANDLE_NOARG_DECL
(
quit
);
HANDLE_NOARG_DECL
(
quit
);
HANDLE_NOARG_DECL
(
help
);
HANDLE_NOARG_DECL
(
help
);
HANDLE_NOARG_DECL
(
shutdown
);
HANDLE_NOARG_DECL
(
shutdown
);
HANDLE_DECL
(
def_exec
);
HANDLE_NOARG_DECL
(
show_exec
);
struct
manager_cmd
commands
[]
=
struct
manager_cmd
commands
[]
=
{
{
{
"ping"
,
"Check if this server is alive"
,
handle_ping
,
4
},
{
"ping"
,
"Check if this server is alive"
,
handle_ping
,
4
},
{
"quit"
,
"Finish session"
,
handle_quit
,
4
},
{
"quit"
,
"Finish session"
,
handle_quit
,
4
},
{
"shutdown"
,
"Shutdown this server"
,
handle_shutdown
,
8
},
{
"shutdown"
,
"Shutdown this server"
,
handle_shutdown
,
8
},
{
"def_exec"
,
"Define executable entry"
,
handle_def_exec
,
8
},
{
"show_exec"
,
"Show defined executable entries"
,
handle_show_exec
,
9
},
{
"help"
,
"Print this message"
,
handle_help
,
4
},
{
"help"
,
"Print this message"
,
handle_help
,
4
},
{
0
,
0
,
0
,
0
}
{
0
,
0
,
0
,
0
}
};
};
...
@@ -158,6 +185,7 @@ struct option long_options[] =
...
@@ -158,6 +185,7 @@ struct option long_options[] =
{
"tcp-backlog"
,
required_argument
,
0
,
'B'
},
{
"tcp-backlog"
,
required_argument
,
0
,
'B'
},
{
"greeting"
,
required_argument
,
0
,
'g'
},
{
"greeting"
,
required_argument
,
0
,
'g'
},
{
"max-command-len"
,
required_argument
,
0
,
'm'
},
{
"max-command-len"
,
required_argument
,
0
,
'm'
},
{
"one-thread"
,
no_argument
,
0
,
'd'
},
{
"version"
,
no_argument
,
0
,
'V'
},
{
"version"
,
no_argument
,
0
,
'V'
},
{
0
,
0
,
0
,
0
}
{
0
,
0
,
0
,
0
}
};
};
...
@@ -190,6 +218,7 @@ static int exec_line(struct manager_thd* thd,char* buf,char* buf_end)
...
@@ -190,6 +218,7 @@ static int exec_line(struct manager_thd* thd,char* buf,char* buf_end)
commands"
);
commands"
);
return
1
;
return
1
;
}
}
for
(;
p
<
buf_end
&&
isspace
(
*
p
);
p
++
);
return
cmd
->
handler_func
(
thd
,
p
,
buf_end
);
return
cmd
->
handler_func
(
thd
,
p
,
buf_end
);
}
}
...
@@ -238,6 +267,78 @@ HANDLE_NOARG_DECL(shutdown)
...
@@ -238,6 +267,78 @@ HANDLE_NOARG_DECL(shutdown)
return
0
;
return
0
;
}
}
HANDLE_DECL
(
def_exec
)
{
struct
manager_exec
*
e
=
0
;
const
char
*
error
=
0
;
if
(
!
(
e
=
manager_exec_new
(
args_start
,
args_end
)))
{
error
=
"Out of memory"
;
goto
err
;
}
if
(
e
->
error
)
{
error
=
e
->
error
;
goto
err
;
}
pthread_mutex_lock
(
&
lock_exec_hash
);
hash_insert
(
&
exec_hash
,(
byte
*
)
e
);
pthread_mutex_unlock
(
&
lock_exec_hash
);
client_msg
(
thd
->
vio
,
MSG_OK
,
"Exec definition created"
);
return
0
;
err:
client_msg
(
thd
->
vio
,
MSG_CLIENT_ERR
,
error
);
if
(
e
)
manager_exec_free
(
e
);
return
1
;
}
HANDLE_NOARG_DECL
(
show_exec
)
{
uint
i
;
client_msg_pre
(
thd
->
vio
,
MSG_INFO
,
"Exec_def
\t
Arguments"
);
pthread_mutex_lock
(
&
lock_exec_hash
);
for
(
i
=
0
;
i
<
exec_hash
.
records
;
i
++
)
{
struct
manager_exec
*
e
=
(
struct
manager_exec
*
)
hash_element
(
&
exec_hash
,
i
);
manager_exec_print
(
thd
->
vio
,
e
);
}
pthread_mutex_unlock
(
&
lock_exec_hash
);
client_msg
(
thd
->
vio
,
MSG_INFO
,
"End"
);
return
0
;
}
static
char
*
arg_strmov
(
char
*
dest
,
const
char
*
src
,
int
n
)
{
char
*
dest_end
=
dest
+
n
-
1
;
char
c
;
for
(;
dest
<
dest_end
&&
(
c
=*
src
++
);)
{
if
(
c
==
'%'
)
*
dest
++=
'%'
;
*
dest
++=
c
;
}
return
dest
;
}
static
void
manager_exec_print
(
Vio
*
vio
,
struct
manager_exec
*
e
)
{
char
buf
[
MAX_CLIENT_MSG_LEN
];
char
*
p
=
buf
,
*
buf_end
=
buf
+
sizeof
(
buf
);
char
**
args
=
e
->
args
;
p
=
arg_strmov
(
p
,
e
->
ident
,(
int
)(
buf_end
-
p
)
-
2
);
*
p
++=
'\t'
;
for
(;
p
<
buf_end
&&
*
args
;
args
++
)
{
p
=
arg_strmov
(
p
,
*
args
,(
int
)(
buf_end
-
p
)
-
2
);
*
p
++=
'\t'
;
}
*
p
=
0
;
client_msg_pre
(
vio
,
MSG_INFO
,
buf
);
return
;
}
static
int
authenticate
(
struct
manager_thd
*
thd
)
static
int
authenticate
(
struct
manager_thd
*
thd
)
{
{
char
*
buf_end
;
char
*
buf_end
;
...
@@ -323,6 +424,8 @@ LOG_MSG_FUNC(info,INFO)
...
@@ -323,6 +424,8 @@ LOG_MSG_FUNC(info,INFO)
#ifndef DBUG_OFF
#ifndef DBUG_OFF
LOG_MSG_FUNC
(
debug
,
DEBUG
)
LOG_MSG_FUNC
(
debug
,
DEBUG
)
#else
inline
void
log_debug
(
char
*
__attribute__
((
unused
))
fmt
,...)
{}
#endif
#endif
static
pthread_handler_decl
(
process_connection
,
arg
)
static
pthread_handler_decl
(
process_connection
,
arg
)
...
@@ -439,7 +542,7 @@ struct manager_thd* manager_thd_new(Vio* vio)
...
@@ -439,7 +542,7 @@ struct manager_thd* manager_thd_new(Vio* vio)
return
tmp
;
return
tmp
;
}
}
void
manager_thd_free
(
struct
manager_thd
*
thd
)
static
void
manager_thd_free
(
struct
manager_thd
*
thd
)
{
{
if
(
thd
->
vio
)
if
(
thd
->
vio
)
vio_close
(
thd
->
vio
);
vio_close
(
thd
->
vio
);
...
@@ -466,11 +569,11 @@ static void clean_up()
...
@@ -466,11 +569,11 @@ static void clean_up()
static
void
print_version
(
void
)
static
void
print_version
(
void
)
{
{
printf
(
"%s Ver %s Distrib %s, for %s (%s)
\n
"
,
my_progname
,
M
NGD
_VERSION
,
printf
(
"%s Ver %s Distrib %s, for %s (%s)
\n
"
,
my_progname
,
M
ANAGER
_VERSION
,
MYSQL_SERVER_VERSION
,
SYSTEM_TYPE
,
MACHINE_TYPE
);
MYSQL_SERVER_VERSION
,
SYSTEM_TYPE
,
MACHINE_TYPE
);
}
}
void
usage
()
static
void
usage
()
{
{
print_version
();
print_version
();
printf
(
"MySQL AB, by Sasha
\n
"
);
printf
(
"MySQL AB, by Sasha
\n
"
);
...
@@ -493,10 +596,10 @@ void usage()
...
@@ -493,10 +596,10 @@ void usage()
-V, --version Output version information and exit.
\n\n
"
);
-V, --version Output version information and exit.
\n\n
"
);
}
}
int
parse_args
(
int
argc
,
char
**
argv
)
static
int
parse_args
(
int
argc
,
char
**
argv
)
{
{
int
c
,
option_index
=
0
;
int
c
,
option_index
=
0
;
while
((
c
=
getopt_long
(
argc
,
argv
,
"P:?#:Vl:b:B:g:m:"
,
while
((
c
=
getopt_long
(
argc
,
argv
,
"P:?#:Vl:b:B:g:m:
d
"
,
long_options
,
&
option_index
))
!=
EOF
)
long_options
,
&
option_index
))
!=
EOF
)
{
{
switch
(
c
)
switch
(
c
)
...
@@ -504,6 +607,9 @@ int parse_args(int argc, char **argv)
...
@@ -504,6 +607,9 @@ int parse_args(int argc, char **argv)
case
'#'
:
case
'#'
:
DBUG_PUSH
(
optarg
?
optarg
:
"d:t:O,/tmp/mysqlmgrd.trace"
);
DBUG_PUSH
(
optarg
?
optarg
:
"d:t:O,/tmp/mysqlmgrd.trace"
);
break
;
break
;
case
'd'
:
one_thread
=
1
;
break
;
case
'P'
:
case
'P'
:
manager_port
=
atoi
(
optarg
);
manager_port
=
atoi
(
optarg
);
break
;
break
;
...
@@ -535,7 +641,7 @@ int parse_args(int argc, char **argv)
...
@@ -535,7 +641,7 @@ int parse_args(int argc, char **argv)
return
0
;
return
0
;
}
}
int
init_server
()
static
int
init_server
()
{
{
int
arg
=
1
;
int
arg
=
1
;
log_info
(
"Started"
);
log_info
(
"Started"
);
...
@@ -554,7 +660,7 @@ int init_server()
...
@@ -554,7 +660,7 @@ int init_server()
return
0
;
return
0
;
}
}
int
run_server_loop
()
static
int
run_server_loop
()
{
{
pthread_t
th
;
pthread_t
th
;
struct
manager_thd
*
thd
;
struct
manager_thd
*
thd
;
...
@@ -598,7 +704,13 @@ int run_server_loop()
...
@@ -598,7 +704,13 @@ int run_server_loop()
}
}
if
(
shutdown_requested
)
if
(
shutdown_requested
)
break
;
break
;
if
(
pthread_create
(
&
th
,
0
,
process_connection
,(
void
*
)
thd
))
if
(
one_thread
)
{
process_connection
((
void
*
)
thd
);
manager_thd_free
(
thd
);
continue
;
}
else
if
(
pthread_create
(
&
th
,
0
,
process_connection
,(
void
*
)
thd
))
{
{
client_msg
(
vio
,
MSG_INTERNAL_ERR
,
"Could not create thread, errno=%d"
,
client_msg
(
vio
,
MSG_INTERNAL_ERR
,
"Could not create thread, errno=%d"
,
errno
);
errno
);
...
@@ -609,7 +721,7 @@ int run_server_loop()
...
@@ -609,7 +721,7 @@ int run_server_loop()
return
0
;
return
0
;
}
}
FILE
*
open_log_stream
()
static
FILE
*
open_log_stream
()
{
{
FILE
*
fp
;
FILE
*
fp
;
if
(
!
(
fp
=
fopen
(
manager_log_file
,
"a"
)))
if
(
!
(
fp
=
fopen
(
manager_log_file
,
"a"
)))
...
@@ -617,7 +729,131 @@ FILE* open_log_stream()
...
@@ -617,7 +729,131 @@ FILE* open_log_stream()
return
fp
;
return
fp
;
}
}
int
daemonize
()
static
byte
*
get_exec_key
(
const
byte
*
e
,
uint
*
len
,
my_bool
__attribute__
((
unused
))
t
)
{
register
const
char
*
key
;
key
=
((
struct
manager_exec
*
)
e
)
->
ident
;
*
len
=
((
struct
manager_exec
*
)
e
)
->
ident_len
;
return
(
byte
*
)
key
;
}
static
void
init_arg_array
(
char
*
arg_str
,
char
**
args
,
uint
arg_count
)
{
char
*
p
=
arg_str
;
for
(;
arg_count
>
0
;
arg_count
--
)
{
*
args
++=
p
;
p
+=
strlen
(
p
)
+
1
;
}
*
args
=
0
;
}
static
uint
tokenize_args
(
char
*
arg_start
,
char
**
arg_end
)
{
char
*
p
,
*
p_write
,
*
p_end
;
uint
arg_count
=
0
;
int
quoted
=
0
,
escaped
=
0
,
last_space
=
0
;
p_end
=*
arg_end
;
p_write
=
p
=
arg_start
;
for
(;
p
<
p_end
;
p
++
)
{
char
c
=
*
p
;
switch
(
c
)
{
case
' '
:
case
'\r'
:
case
'\n'
:
if
(
!
quoted
)
{
if
(
!
last_space
)
{
*
p_write
++=
0
;
arg_count
++
;
last_space
=
1
;
}
}
else
*
p_write
++=
c
;
escaped
=
0
;
break
;
case
'"'
:
if
(
!
escaped
)
quoted
=!
quoted
;
else
*
p_write
++=
c
;
last_space
=
0
;
escaped
=
0
;
break
;
case
'\\'
:
if
(
!
escaped
)
escaped
=
1
;
else
{
*
p_write
++=
c
;
escaped
=
0
;
}
last_space
=
0
;
break
;
default:
escaped
=
last_space
=
0
;
*
p_write
++=
c
;
break
;
}
}
if
(
!
last_space
&&
p_write
>
arg_start
)
arg_count
++
;
*
p_write
=
0
;
*
arg_end
=
p_write
;
log_debug
(
"arg_count=%d,arg_start='%s'"
,
arg_count
,
arg_start
);
return
arg_count
;
}
static
struct
manager_exec
*
manager_exec_new
(
char
*
arg_start
,
char
*
arg_end
)
{
struct
manager_exec
*
tmp
;
char
*
first_arg
;
uint
arg_len
,
num_args
;
num_args
=
tokenize_args
(
arg_start
,
&
arg_end
);
arg_len
=
(
uint
)(
arg_end
-
arg_start
)
+
1
;
/* include \0 terminator*/
if
(
!
(
tmp
=
(
struct
manager_exec
*
)
my_malloc
(
sizeof
(
*
tmp
)
+
arg_len
+
sizeof
(
char
*
)
*
num_args
,
MYF
(
0
))))
return
0
;
if
(
num_args
<
2
)
{
tmp
->
error
=
"Too few arguments"
;
return
tmp
;
}
tmp
->
data_buf
=
(
char
*
)
tmp
+
sizeof
(
*
tmp
);
memcpy
(
tmp
->
data_buf
,
arg_start
,
arg_len
);
tmp
->
args
=
(
char
**
)(
tmp
->
data_buf
+
arg_len
);
tmp
->
ident
=
tmp
->
data_buf
;
tmp
->
ident_len
=
strlen
(
tmp
->
ident
);
first_arg
=
tmp
->
ident
+
tmp
->
ident_len
+
1
;
init_arg_array
(
first_arg
,
tmp
->
args
,
num_args
-
1
);
strmov
(
tmp
->
con_user
,
"root"
);
tmp
->
con_pass
[
0
]
=
0
;
tmp
->
con_sock
[
0
]
=
0
;
tmp
->
con_port
=
MYSQL_PORT
;
tmp
->
bin_path
=
tmp
->
args
[
0
];
tmp
->
error
=
0
;
return
tmp
;
}
static
void
manager_exec_free
(
void
*
e
)
{
my_free
(
e
,
MYF
(
0
));
}
static
void
init_globals
()
{
if
(
hash_init
(
&
exec_hash
,
1024
,
0
,
0
,
get_exec_key
,
manager_exec_free
,
MYF
(
0
)))
die
(
"Exec hash initialization failed"
);
}
static
int
daemonize
()
{
{
switch
(
fork
())
switch
(
fork
())
{
{
...
@@ -625,8 +861,10 @@ int daemonize()
...
@@ -625,8 +861,10 @@ int daemonize()
die
(
"Cannot fork"
);
die
(
"Cannot fork"
);
case
0
:
case
0
:
errfp
=
open_log_stream
();
errfp
=
open_log_stream
();
init_globals
();
close
(
0
);
close
(
0
);
close
(
1
);
close
(
1
);
close
(
2
);
init_server
();
init_server
();
run_server_loop
();
run_server_loop
();
clean_up
();
clean_up
();
...
@@ -644,8 +882,24 @@ int main(int argc, char** argv)
...
@@ -644,8 +882,24 @@ int main(int argc, char** argv)
parse_args
(
argc
,
argv
);
parse_args
(
argc
,
argv
);
pthread_mutex_init
(
&
lock_log
,
0
);
pthread_mutex_init
(
&
lock_log
,
0
);
pthread_mutex_init
(
&
lock_shutdown
,
0
);
pthread_mutex_init
(
&
lock_shutdown
,
0
);
pthread_mutex_init
(
&
lock_exec_hash
,
0
);
if
(
one_thread
)
{
init_globals
();
init_server
();
run_server_loop
();
clean_up
();
return
0
;
}
else
return
daemonize
();
return
daemonize
();
}
}
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