Skip to content
GitLab
Explore
Sign in
Register
Primary navigation
Search or go to…
Project
Synchronet
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package registry
Container registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Main
Synchronet
Commits
3598a794
Commit
3598a794
authored
23 years ago
by
rswindell
Browse files
Options
Downloads
Patches
Plain Diff
New wrapper module targeted specifically at directory-related RTL functions.
parent
25bf73b8
Branches
Branches containing commit
Tags
Tags containing commit
No related merge requests found
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
src/sbbs3/dirwrap.c
+469
-0
469 additions, 0 deletions
src/sbbs3/dirwrap.c
src/sbbs3/dirwrap.h
+212
-0
212 additions, 0 deletions
src/sbbs3/dirwrap.h
with
681 additions
and
0 deletions
src/sbbs3/dirwrap.c
0 → 100644
+
469
−
0
View file @
3598a794
/* dirwrap.c */
/* Directory-related system-call wrappers */
/* $Id$ */
/****************************************************************************
* @format.tab-size 4 (Plain Text/Source Code File Header) *
* @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) *
* *
* Copyright 2000 Rob Swindell - http://www.synchro.net/copyright.html *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
* as published by the Free Software Foundation; either version 2 *
* of the License, or (at your option) any later version. *
* See the GNU General Public License for more details: gpl.txt or *
* http://www.fsf.org/copyleft/gpl.html *
* *
* Anonymous FTP access to the most recent released source is available at *
* ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net *
* *
* Anonymous CVS access to the development source and modification history *
* is available at cvs.synchro.net:/cvsroot/sbbs, example: *
* cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login *
* (just hit return, no password is necessary) *
* cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src *
* *
* For Synchronet coding style and modification guidelines, see *
* http://www.synchro.net/source.html *
* *
* You are encouraged to submit any modifications (preferably in Unix diff *
* format) via e-mail to mods@synchro.net *
* *
* Note: If this box doesn't appear square, then you need to fix your tabs. *
****************************************************************************/
#ifdef _WIN32
#include
<windows.h>
/* WINAPI, etc */
#include
<io.h>
/* _findfirst */
#elif defined __unix__
#include
<unistd.h>
/* usleep */
#include
<fcntl.h>
/* O_NOCCTY */
#include
<ctype.h>
/* toupper */
#ifdef __FreeBSD__
#include
<sys/param.h>
#include
<sys/mount.h>
#include
<sys/kbio.h>
#endif
#include
<sys/ioctl.h>
/* ioctl */
#ifdef __GLIBC__
/* actually, BSD, but will work for now */
#include
<sys/vfs.h>
/* statfs() */
#endif
#endif
#include
<sys/types.h>
/* _dev_t */
#include
<sys/stat.h>
/* struct stat */
#include
<stdio.h>
/* sprintf */
#include
<stdlib.h>
/* rand */
#include
<errno.h>
/* ENOENT definitions */
#include
"dirwrap.h"
/* DLLCALL */
/****************************************************************************/
/* Return the filename portion of a full pathname */
/****************************************************************************/
char
*
DLLCALL
getfname
(
char
*
path
)
{
char
*
fname
;
fname
=
strrchr
(
path
,
'/'
);
if
(
fname
==
NULL
)
fname
=
strrchr
(
path
,
'\\'
);
if
(
fname
!=
NULL
)
fname
++
;
else
fname
=
path
;
return
(
fname
);
}
#if !defined(__unix__)
/****************************************************************************/
/* Win32 (minimal) implementation of POSIX.2 glob() function */
/* This code _may_ work on other DOS-based platforms (e.g. OS/2) */
/****************************************************************************/
static
int
glob_compare
(
const
void
*
arg1
,
const
void
*
arg2
)
{
/* Compare all of both strings: */
return
stricmp
(
*
(
char
**
)
arg1
,
*
(
char
**
)
arg2
);
}
#ifdef __BORLANDC__
#pragma argsused
#endif
int
DLLCALL
glob
(
const
char
*
pattern
,
int
flags
,
void
*
unused
,
glob_t
*
glob
)
{
struct
_finddata_t
ff
;
long
ff_handle
;
size_t
found
=
0
;
char
path
[
MAX_PATH
+
1
];
char
*
p
;
char
**
new_pathv
;
if
(
!
(
flags
&
GLOB_APPEND
))
{
glob
->
gl_pathc
=
0
;
glob
->
gl_pathv
=
NULL
;
}
ff_handle
=
_findfirst
((
char
*
)
pattern
,
&
ff
);
while
(
ff_handle
!=-
1
)
{
if
(
!
(
flags
&
GLOB_ONLYDIR
)
||
ff
.
attrib
&
_A_SUBDIR
)
{
if
((
new_pathv
=
realloc
(
glob
->
gl_pathv
,(
glob
->
gl_pathc
+
1
)
*
sizeof
(
char
*
)))
==
NULL
)
{
globfree
(
glob
);
return
(
GLOB_NOSPACE
);
}
glob
->
gl_pathv
=
new_pathv
;
/* build the full pathname */
strcpy
(
path
,
pattern
);
p
=
getfname
(
path
);
*
p
=
0
;
strcat
(
path
,
ff
.
name
);
if
((
glob
->
gl_pathv
[
glob
->
gl_pathc
]
=
malloc
(
strlen
(
path
)
+
2
))
==
NULL
)
{
globfree
(
glob
);
return
(
GLOB_NOSPACE
);
}
strcpy
(
glob
->
gl_pathv
[
glob
->
gl_pathc
],
path
);
if
(
flags
&
GLOB_MARK
&&
ff
.
attrib
&
_A_SUBDIR
)
strcat
(
glob
->
gl_pathv
[
glob
->
gl_pathc
],
"/"
);
glob
->
gl_pathc
++
;
found
++
;
}
if
(
_findnext
(
ff_handle
,
&
ff
)
!=
0
)
{
_findclose
(
ff_handle
);
ff_handle
=-
1
;
}
}
if
(
found
==
0
)
return
(
GLOB_NOMATCH
);
if
(
!
(
flags
&
GLOB_NOSORT
))
{
qsort
(
glob
->
gl_pathv
,
found
,
sizeof
(
char
*
),
glob_compare
);
}
return
(
0
);
/* success */
}
void
DLLCALL
globfree
(
glob_t
*
glob
)
{
size_t
i
;
if
(
glob
==
NULL
)
return
;
if
(
glob
->
gl_pathv
!=
NULL
)
{
for
(
i
=
0
;
i
<
glob
->
gl_pathc
;
i
++
)
if
(
glob
->
gl_pathv
[
i
]
!=
NULL
)
free
(
glob
->
gl_pathv
[
i
]);
free
(
glob
->
gl_pathv
);
glob
->
gl_pathv
=
NULL
;
}
glob
->
gl_pathc
=
0
;
}
#endif
/* !defined(__unix__) */
/****************************************************************************/
/* POSIX directory operations using Microsoft _findfirst/next API. */
/****************************************************************************/
#if defined(_MSC_VER)
DIR
*
opendir
(
const
char
*
dirname
)
{
DIR
*
dir
;
if
((
dir
=
(
DIR
*
)
calloc
(
1
,
sizeof
(
DIR
)))
==
NULL
)
{
errno
=
ENOMEM
;
return
(
NULL
);
}
sprintf
(
dir
->
filespec
,
"%.*s"
,
sizeof
(
dir
->
filespec
)
-
5
,
dirname
);
if
(
*
dir
->
filespec
&&
dir
->
filespec
[
strlen
(
dir
->
filespec
)
-
1
]
!=
'\\'
)
strcat
(
dir
->
filespec
,
"
\\
"
);
strcat
(
dir
->
filespec
,
"*.*"
);
dir
->
handle
=
_findfirst
(
dir
->
filespec
,
&
dir
->
finddata
);
if
(
dir
->
handle
==-
1
)
{
errno
=
ENOENT
;
free
(
dir
);
return
(
NULL
);
}
return
(
dir
);
}
struct
dirent
*
readdir
(
DIR
*
dir
)
{
if
(
dir
==
NULL
)
return
(
NULL
);
if
(
dir
->
end
==
TRUE
)
return
(
NULL
);
if
(
dir
->
handle
==-
1
)
return
(
NULL
);
sprintf
(
dir
->
dirent
.
d_name
,
"%.*s"
,
sizeof
(
struct
dirent
)
-
1
,
dir
->
finddata
.
name
);
if
(
_findnext
(
dir
->
handle
,
&
dir
->
finddata
)
!=
0
)
dir
->
end
=
TRUE
;
return
(
&
dir
->
dirent
);
}
int
closedir
(
DIR
*
dir
)
{
if
(
dir
==
NULL
)
return
(
-
1
);
_findclose
(
dir
->
handle
);
free
(
dir
);
return
(
0
);
}
void
rewinddir
(
DIR
*
dir
)
{
if
(
dir
==
NULL
)
return
;
_findclose
(
dir
->
handle
);
dir
->
end
=
FALSE
;
dir
->
handle
=
_findfirst
(
dir
->
filespec
,
&
dir
->
finddata
);
}
#endif
/* defined(_MSC_VER) */
/****************************************************************************/
/* Returns the time/date of the file in 'filename' in time_t (unix) format */
/****************************************************************************/
time_t
DLLCALL
fdate
(
char
*
filename
)
{
struct
stat
st
;
if
(
access
(
filename
,
0
)
==-
1
)
return
(
-
1
);
if
(
stat
(
filename
,
&
st
)
!=
0
)
return
(
-
1
);
return
(
st
.
st_mtime
);
}
/****************************************************************************/
/* Returns the length of the file in 'filename' */
/****************************************************************************/
long
DLLCALL
flength
(
char
*
filename
)
{
#ifdef __BORLANDC__
/* stat() doesn't work right */
long
handle
;
struct
_finddata_t
f
;
if
(
access
(
filename
,
0
)
==-
1
)
return
(
-
1L
);
if
((
handle
=
_findfirst
(
filename
,
&
f
))
==-
1
)
return
(
-
1
);
_findclose
(
handle
);
return
(
f
.
size
);
#else
struct
stat
st
;
if
(
access
(
filename
,
0
)
==-
1
)
return
(
-
1L
);
if
(
stat
(
filename
,
&
st
)
!=
0
)
return
(
-
1L
);
return
(
st
.
st_size
);
#endif
}
/****************************************************************************/
/* Checks the file system for the existence of one or more files. */
/* Returns TRUE if it exists, FALSE if it doesn't. */
/* 'filespec' may contain wildcards! */
/****************************************************************************/
BOOL
DLLCALL
fexist
(
char
*
filespec
)
{
#ifdef _WIN32
long
handle
;
struct
_finddata_t
f
;
if
(
access
(
filespec
,
0
)
==-
1
&&
!
strchr
(
filespec
,
'*'
)
&&
!
strchr
(
filespec
,
'?'
))
return
(
FALSE
);
if
((
handle
=
_findfirst
(
filespec
,
&
f
))
==-
1
)
return
(
FALSE
);
_findclose
(
handle
);
if
(
f
.
attrib
&
_A_SUBDIR
)
return
(
FALSE
);
return
(
TRUE
);
#elif defined(__unix__)
/* portion by cmartin */
glob_t
g
;
int
c
;
int
l
;
if
(
access
(
filespec
,
0
)
==-
1
&&
!
strchr
(
filespec
,
'*'
)
&&
!
strchr
(
filespec
,
'?'
))
return
(
FALSE
);
// start the search
glob
(
filespec
,
GLOB_MARK
|
GLOB_NOSORT
,
NULL
,
&
g
);
if
(
!
g
.
gl_pathc
)
{
// no results
globfree
(
&
g
);
return
FALSE
;
}
// make sure it's not a directory
c
=
g
.
gl_pathc
;
while
(
c
--
)
{
l
=
strlen
(
g
.
gl_pathv
[
c
]);
if
(
l
&&
g
.
gl_pathv
[
c
][
l
-
1
]
!=
'/'
)
{
globfree
(
&
g
);
return
TRUE
;
}
}
globfree
(
&
g
);
return
FALSE
;
#else
#warning "fexist() port needs to support wildcards!"
return
(
FALSE
);
#endif
}
/****************************************************************************/
/* Returns TRUE if the filename specified is a directory */
/****************************************************************************/
BOOL
DLLCALL
isdir
(
char
*
filename
)
{
struct
stat
st
;
if
(
stat
(
filename
,
&
st
)
!=
0
)
return
(
FALSE
);
return
((
st
.
st_mode
&
S_IFDIR
)
?
TRUE
:
FALSE
);
}
/****************************************************************************/
/* Returns the attributes (mode) for specified 'filename' */
/****************************************************************************/
int
DLLCALL
getfattr
(
char
*
filename
)
{
#ifdef _WIN32
long
handle
;
struct
_finddata_t
finddata
;
if
((
handle
=
_findfirst
(
filename
,
&
finddata
))
==-
1
)
{
errno
=
ENOENT
;
return
(
-
1
);
}
_findclose
(
handle
);
return
(
finddata
.
attrib
);
#else
struct
stat
st
;
if
(
stat
(
filename
,
&
st
)
!=
0
)
{
errno
=
ENOENT
;
return
(
-
1L
);
}
return
(
st
.
st_mode
);
#endif
}
/****************************************************************************/
/* Return free disk space in bytes (up to a maximum of 4GB) */
/****************************************************************************/
#ifdef _WIN32
typedef
BOOL
(
WINAPI
*
GetDiskFreeSpaceEx_t
)
(
LPCTSTR
,
PULARGE_INTEGER
,
PULARGE_INTEGER
,
PULARGE_INTEGER
);
#endif
ulong
DLLCALL
getfreediskspace
(
char
*
path
)
{
#ifdef _WIN32
char
root
[
16
];
DWORD
TotalNumberOfClusters
;
DWORD
NumberOfFreeClusters
;
DWORD
BytesPerSector
;
DWORD
SectorsPerCluster
;
ULARGE_INTEGER
avail
;
ULARGE_INTEGER
size
;
static
HINSTANCE
hK32
;
GetDiskFreeSpaceEx_t
GetDiskFreeSpaceEx
;
if
(
hK32
==
NULL
)
hK32
=
LoadLibrary
(
"KERNEL32"
);
GetDiskFreeSpaceEx
=
(
GetDiskFreeSpaceEx_t
)
GetProcAddress
(
hK32
,
"GetDiskFreeSpaceExA"
);
if
(
GetDiskFreeSpaceEx
!=
NULL
)
{
/* Windows 95-OSR2 or later */
if
(
!
GetDiskFreeSpaceEx
(
path
,
// pointer to the directory name
&
avail
,
// receives the number of bytes on disk avail to the caller
&
size
,
// receives the number of bytes on disk
NULL
))
// receives the free bytes on disk
return
(
0
);
#ifdef _ANONYMOUS_STRUCT
if
(
avail
.
HighPart
)
#else
if
(
avail
.
u
.
HighPart
)
#endif
return
(
0xffffffff
);
/* 4GB max */
#ifdef _ANONYMOUS_STRUCT
return
(
avail
.
LowPart
);
#else
return
(
avail
.
u
.
LowPart
);
#endif
}
/* Windows 95 (old way), limited to 2GB */
sprintf
(
root
,
"%.3s"
,
path
);
if
(
!
GetDiskFreeSpace
(
root
,
// pointer to root path
&
SectorsPerCluster
,
// pointer to sectors per cluster
&
BytesPerSector
,
// pointer to bytes per sector
&
NumberOfFreeClusters
,
// pointer to number of free clusters
&
TotalNumberOfClusters
// pointer to total number of clusters
))
return
(
0
);
return
(
NumberOfFreeClusters
*
SectorsPerCluster
*
BytesPerSector
);
/* statfs is also used under FreeBSD */
#elif defined(__GLIBC__) || defined(__FreeBSD__)
struct
statfs
fs
;
if
(
statfs
(
path
,
&
fs
)
<
0
)
return
0
;
return
fs
.
f_bsize
*
fs
.
f_bavail
;
#else
#warning OS-specific code needed here
return
(
0
);
#endif
}
This diff is collapsed.
Click to expand it.
src/sbbs3/dirwrap.h
0 → 100644
+
212
−
0
View file @
3598a794
/* dirwrap.h */
/* Directory system-call wrappers */
/* $Id$ */
/****************************************************************************
* @format.tab-size 4 (Plain Text/Source Code File Header) *
* @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) *
* *
* Copyright 2000 Rob Swindell - http://www.synchro.net/copyright.html *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
* as published by the Free Software Foundation; either version 2 *
* of the License, or (at your option) any later version. *
* See the GNU General Public License for more details: gpl.txt or *
* http://www.fsf.org/copyleft/gpl.html *
* *
* Anonymous FTP access to the most recent released source is available at *
* ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net *
* *
* Anonymous CVS access to the development source and modification history *
* is available at cvs.synchro.net:/cvsroot/sbbs, example: *
* cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login *
* (just hit return, no password is necessary) *
* cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src *
* *
* For Synchronet coding style and modification guidelines, see *
* http://www.synchro.net/source.html *
* *
* You are encouraged to submit any modifications (preferably in Unix diff *
* format) via e-mail to mods@synchro.net *
* *
* Note: If this box doesn't appear square, then you need to fix your tabs. *
****************************************************************************/
#ifndef _DIRWRAP_H
#define _DIRWRAP_H
#include
"gen_defs.h"
/* ulong */
#ifdef DLLEXPORT
#undef DLLEXPORT
#endif
#ifdef DLLCALL
#undef DLLCALL
#endif
#ifdef _WIN32
#ifdef WRAPPER_DLL
#define DLLEXPORT __declspec(dllexport)
#else
#define DLLEXPORT __declspec(dllimport)
#endif
#ifdef __BORLANDC__
#define DLLCALL __stdcall
#else
#define DLLCALL
#endif
#else
/* !_WIN32 */
#define DLLEXPORT
#define DLLCALL
#endif
#ifdef __cplusplus
extern
"C"
{
#endif
#ifdef _MSC_VER
#include
"msdirent.h"
#else
#include
<dirent.h>
/* POSIX directory functions */
#endif
/****************/
/* RTL-specific */
/****************/
#ifdef __unix__
#define ALLFILES "*"
/* matches all files in a directory */
#define MKDIR(dir) _mkdir(dir)
#define RMDIR(dir) _rmdir(dir)
#define FULLPATH(a,r,l) _fullpath(a,r,l)
#include
<glob.h>
/* POSIX.2 directory pattern matching function */
#else
#define ALLFILES "*.*"
/* matches all files in a directory */
#define MKDIR(dir) mkdir(dir,0777)
#define RMDIR(dir) rmdir(dir)
#define FULLPATH(a,r,l) realpath(r,a)
/* glob-compatible findfirst/findnext wrapper */
typedef
struct
{
size_t
gl_pathc
;
/* Count of paths matched so far */
char
**
gl_pathv
;
/* List of matched pathnames. */
size_t
gl_offs
;
/* Slots to reserve in 'gl_pathv'. */
}
glob_t
;
/* Bits set in the FLAGS argument to `glob'. */
#define GLOB_ERR (1 << 0)
/* Return on read errors. */
#define GLOB_MARK (1 << 1)
/* Append a slash to each name. */
#define GLOB_NOSORT (1 << 2)
/* Don't sort the names. */
#define GLOB_DOOFFS (1 << 3)
/* Insert PGLOB->gl_offs NULLs. */
#define GLOB_NOCHECK (1 << 4)
/* If nothing matches, return the pattern. */
#define GLOB_APPEND (1 << 5)
/* Append to results of a previous call. */
#define GLOB_NOESCAPE (1 << 6)
/* Backslashes don't quote metacharacters. */
#define GLOB_PERIOD (1 << 7)
/* Leading `.' can be matched by metachars. */
#define GLOB_MAGCHAR (1 << 8)
/* Set in gl_flags if any metachars seen. */
#define GLOB_ALTDIRFUNC (1 << 9)
/* Use gl_opendir et al functions. */
#define GLOB_BRACE (1 << 10)
/* Expand "{a,b}" to "a" "b". */
#define GLOB_NOMAGIC (1 << 11)
/* If no magic chars, return the pattern. */
#define GLOB_TILDE (1 << 12)
/* Expand ~user and ~ to home directories. */
#define GLOB_ONLYDIR (1 << 13)
/* Match only directories. */
#define GLOB_TILDE_CHECK (1 << 14)
/* Like GLOB_TILDE but return an error
if the user name is not available. */
/* Error returns from `glob'. */
#define GLOB_NOSPACE 1
/* Ran out of memory. */
#define GLOB_ABORTED 2
/* Read error. */
#define GLOB_NOMATCH 3
/* No matches found. */
#define GLOB_NOSYS 4
/* Not implemented. */
DLLEXPORT
int
DLLCALL
glob
(
const
char
*
pattern
,
int
flags
,
void
*
unused
,
glob_t
*
);
DLLEXPORT
void
DLLCALL
globfree
(
glob_t
*
);
#endif
/*****************************/
/* POSIX Directory Functions */
/*****************************/
#if defined(_MSC_VER)
/* dirent structure returned by readdir().
*/
struct
dirent
{
char
d_name
[
260
];
/* filename */
};
/* DIR type returned by opendir(). The members of this structure
* must not be accessed by application programs.
*/
typedef
struct
{
char
filespec
[
260
];
struct
dirent
dirent
;
long
handle
;
struct
_finddata_t
finddata
;
BOOL
end
;
/* End of directory flag */
}
DIR
;
/* Prototypes.
*/
DIR
*
opendir
(
const
char
*
__dirname
);
struct
dirent
*
readdir
(
DIR
*
__dir
);
int
closedir
(
DIR
*
__dir
);
void
rewinddir
(
DIR
*
__dir
);
#endif
/**********/
/* Macros */
/**********/
/* POSIX readdir convenience macro */
#ifndef DIRENT
#define DIRENT struct dirent
#endif
#if defined(__unix__)
#define BACKSLASH '/'
#else
/* MS-DOS based OS */
#define BACKSLASH '\\'
#endif
#if defined(_MSC_VER) || defined(__MINGW32__)
#define CHMOD(s,m) _chmod(s,m)
#define PUTENV _putenv
#define GETCWD _getcwd
#elif defined(__BORLANDC__)
#define CHMOD(p,a) _rtl_chmod(p,1,a)
/* _chmod obsolete in 4.x */
#define PUTENV putenv
#define GETCWD getcwd
#else
/* ??? */
#define CHMOD(s,m) chmod(s,m)
#define PUTENV putenv
#define GETCWD getcwd
#endif
/* General file system wrappers for all platforms and compilers */
DLLEXPORT
BOOL
DLLCALL
fexist
(
char
*
filespec
);
DLLEXPORT
long
DLLCALL
flength
(
char
*
filename
);
DLLEXPORT
time_t
DLLCALL
fdate
(
char
*
filename
);
DLLEXPORT
BOOL
DLLCALL
isdir
(
char
*
filename
);
DLLEXPORT
char
*
DLLCALL
getfname
(
char
*
path
);
DLLEXPORT
int
DLLCALL
getfattr
(
char
*
filename
);
DLLEXPORT
ulong
DLLCALL
getfreediskspace
(
char
*
path
);
#ifdef __cplusplus
}
#endif
#endif
/* Don't add anything after this line */
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment