mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-06 23:26:39 +00:00
Fix directory/path manipulation methods
The `dirname` and `dirbase` methods have been replaced with the Android implementation, which is actually compilant to some kind of standard. A new method `topdir` has been added, which returns the topmost directory in a path. These changes fix issue #49: `gitfo_prettify_dir_path` converts "./.git/" to ".git/", so the code at src/repository.c:190 goes out of bounds when trying to find the topmost directory. The new `git__topdir` method handles this gracefully, and the fixed `git__dirname` now returns the proper value for the repository's working dir. E.g. /repo/.git/ ==> working dir '/repo/' .git/ ==> working dir '.' Signed-off-by: Vicent Marti <tanoku@gmail.com>
This commit is contained in:
parent
c836c332f1
commit
f725931b48
@ -59,7 +59,7 @@ static int make_temp_file(git_file *fd, char *tmp, size_t n, char *file)
|
|||||||
size_t tmplen = strlen(template);
|
size_t tmplen = strlen(template);
|
||||||
int dirlen;
|
int dirlen;
|
||||||
|
|
||||||
if ((dirlen = git__dirname(tmp, n, file)) < 0)
|
if ((dirlen = git__dirname_r(tmp, n, file)) < 0)
|
||||||
return GIT_ERROR;
|
return GIT_ERROR;
|
||||||
|
|
||||||
if ((dirlen + tmplen) >= n)
|
if ((dirlen + tmplen) >= n)
|
||||||
|
@ -157,7 +157,9 @@ static int assign_repository_DIRs(git_repository *repo,
|
|||||||
|
|
||||||
static int guess_repository_DIRs(git_repository *repo, const char *repository_path)
|
static int guess_repository_DIRs(git_repository *repo, const char *repository_path)
|
||||||
{
|
{
|
||||||
char path_aux[GIT_PATH_MAX], *last_DIR;
|
char path_aux[GIT_PATH_MAX];
|
||||||
|
const char *topdir;
|
||||||
|
|
||||||
int path_len;
|
int path_len;
|
||||||
int error = GIT_SUCCESS;
|
int error = GIT_SUCCESS;
|
||||||
|
|
||||||
@ -185,12 +187,10 @@ static int guess_repository_DIRs(git_repository *repo, const char *repository_pa
|
|||||||
|
|
||||||
path_aux[path_len] = 0;
|
path_aux[path_len] = 0;
|
||||||
|
|
||||||
last_DIR = (path_aux + path_len - 2);
|
if ((topdir = git__topdir(path_aux)) == NULL)
|
||||||
|
return GIT_EINVALIDPATH;
|
||||||
|
|
||||||
while (*last_DIR != '/')
|
if (strcmp(topdir, GIT_DIR) == 0) {
|
||||||
last_DIR--;
|
|
||||||
|
|
||||||
if (strcmp(last_DIR, GIT_DIR) == 0) {
|
|
||||||
repo->is_bare = 0;
|
repo->is_bare = 0;
|
||||||
|
|
||||||
/* index file */
|
/* index file */
|
||||||
@ -198,8 +198,9 @@ static int guess_repository_DIRs(git_repository *repo, const char *repository_pa
|
|||||||
repo->path_index = git__strdup(path_aux);
|
repo->path_index = git__strdup(path_aux);
|
||||||
|
|
||||||
/* working dir */
|
/* working dir */
|
||||||
*(last_DIR + 1) = 0;
|
repo->path_workdir = git__dirname(path_aux);
|
||||||
repo->path_workdir = git__strdup(path_aux);
|
if (repo->path_workdir == NULL)
|
||||||
|
return GIT_EINVALIDPATH;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
repo->is_bare = 1;
|
repo->is_bare = 1;
|
||||||
|
208
src/util.c
208
src/util.c
@ -36,65 +36,193 @@ int git__suffixcmp(const char *str, const char *suffix)
|
|||||||
return strcmp(str + (a - b), suffix);
|
return strcmp(str + (a - b), suffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
int git__dirname(char *dir, size_t n, char *path)
|
/*
|
||||||
|
* Based on the Android implementation, BSD licensed.
|
||||||
|
* Check http://android.git.kernel.org/
|
||||||
|
*/
|
||||||
|
int git__basename_r(char *buffer, size_t bufflen, const char *path)
|
||||||
{
|
{
|
||||||
char *s;
|
const char *endp, *startp;
|
||||||
size_t len;
|
int len, result;
|
||||||
|
|
||||||
assert(dir && n > 1);
|
/* Empty or NULL string gets treated as "." */
|
||||||
|
if (path == NULL || *path == '\0') {
|
||||||
if (!path || !*path || (s = strrchr(path, '/')) == NULL) {
|
startp = ".";
|
||||||
strcpy(dir, ".");
|
len = 1;
|
||||||
return 1;
|
goto Exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s == path) { /* "/[aaa]" */
|
/* Strip trailing slashes */
|
||||||
strcpy(dir, "/");
|
endp = path + strlen(path) - 1;
|
||||||
return 1;
|
while (endp > path && *endp == '/')
|
||||||
|
endp--;
|
||||||
|
|
||||||
|
/* All slashes becomes "/" */
|
||||||
|
if (endp == path && *endp == '/') {
|
||||||
|
startp = "/";
|
||||||
|
len = 1;
|
||||||
|
goto Exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((len = s - path) >= n)
|
/* Find the start of the base */
|
||||||
return GIT_ERROR;
|
startp = endp;
|
||||||
|
while (startp > path && *(startp - 1) != '/')
|
||||||
|
startp--;
|
||||||
|
|
||||||
memcpy(dir, path, len);
|
len = endp - startp +1;
|
||||||
dir[len] = '\0';
|
|
||||||
|
|
||||||
return len;
|
Exit:
|
||||||
|
result = len;
|
||||||
|
if (buffer == NULL) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (len > (int)bufflen-1) {
|
||||||
|
len = (int)bufflen-1;
|
||||||
|
result = GIT_ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len >= 0) {
|
||||||
|
memcpy(buffer, startp, len);
|
||||||
|
buffer[len] = 0;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
int git__basename(char *base, size_t n, char *path)
|
/*
|
||||||
|
* Based on the Android implementation, BSD licensed.
|
||||||
|
* Check http://android.git.kernel.org/
|
||||||
|
*/
|
||||||
|
int git__dirname_r(char *buffer, size_t bufflen, const char *path)
|
||||||
{
|
{
|
||||||
char *s;
|
const char *endp;
|
||||||
size_t len;
|
int result, len;
|
||||||
|
|
||||||
assert(base && n > 1);
|
/* Empty or NULL string gets treated as "." */
|
||||||
|
if (path == NULL || *path == '\0') {
|
||||||
if (!path || !*path) {
|
path = ".";
|
||||||
strcpy(base, ".");
|
len = 1;
|
||||||
return 1;
|
goto Exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Strip trailing slashes */
|
||||||
|
endp = path + strlen(path) - 1;
|
||||||
|
while (endp > path && *endp == '/')
|
||||||
|
endp--;
|
||||||
|
|
||||||
|
/* Find the start of the dir */
|
||||||
|
while (endp > path && *endp != '/')
|
||||||
|
endp--;
|
||||||
|
|
||||||
|
/* Either the dir is "/" or there are no slashes */
|
||||||
|
if (endp == path) {
|
||||||
|
path = (*endp == '/') ? "/" : ".";
|
||||||
|
len = 1;
|
||||||
|
goto Exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
endp--;
|
||||||
|
} while (endp > path && *endp == '/');
|
||||||
|
|
||||||
|
len = endp - path +1;
|
||||||
|
|
||||||
|
Exit:
|
||||||
|
result = len;
|
||||||
|
if (len+1 > GIT_PATH_MAX) {
|
||||||
|
return GIT_ENOMEM;
|
||||||
|
}
|
||||||
|
if (buffer == NULL)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
if (len > (int)bufflen-1) {
|
||||||
|
len = (int)bufflen-1;
|
||||||
|
result = GIT_ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len >= 0) {
|
||||||
|
memcpy(buffer, path, len);
|
||||||
|
buffer[len] = 0;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
char *git__dirname(const char *path)
|
||||||
|
{
|
||||||
|
char *dname = NULL;
|
||||||
|
int len;
|
||||||
|
|
||||||
|
len = (path ? strlen(path) : 0) + 2;
|
||||||
|
dname = (char *)git__malloc(len);
|
||||||
|
if (dname == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (git__dirname_r(dname, len, path) < GIT_SUCCESS) {
|
||||||
|
free(dname);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dname;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *git__basename(const char *path)
|
||||||
|
{
|
||||||
|
char *bname = NULL;
|
||||||
|
int len;
|
||||||
|
|
||||||
|
len = (path ? strlen(path) : 0) + 2;
|
||||||
|
bname = (char *)git__malloc(len);
|
||||||
|
if (bname == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (git__basename_r(bname, len, path) < GIT_SUCCESS) {
|
||||||
|
free(bname);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bname;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const char *git__topdir(const char *path)
|
||||||
|
{
|
||||||
|
size_t len;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
assert(path);
|
||||||
len = strlen(path);
|
len = strlen(path);
|
||||||
|
|
||||||
if ((s = strrchr(path, '/')) == NULL) {
|
if (!len || path[len - 1] != '/')
|
||||||
if (len >= n)
|
return NULL;
|
||||||
return GIT_ERROR;
|
|
||||||
strcpy(base, path);
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s == path && len == 1) { /* "/" */
|
for (i = len - 2; i >= 0; --i)
|
||||||
strcpy(base, "/");
|
if (path[i] == '/')
|
||||||
return 1;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
len -= s - path;
|
return &path[i + 1];
|
||||||
if (len >= n)
|
}
|
||||||
return GIT_ERROR;
|
|
||||||
|
|
||||||
memcpy(base, s+1, len);
|
static char *strtok_raw(char *output, char *src, char *delimit, int keep)
|
||||||
base[len] = '\0';
|
{
|
||||||
|
while (*src && strchr(delimit, *src) == NULL)
|
||||||
|
*output++ = *src++;
|
||||||
|
|
||||||
return len;
|
*output = 0;
|
||||||
|
|
||||||
|
if (keep)
|
||||||
|
return src;
|
||||||
|
else
|
||||||
|
return *src ? src+1 : src;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *git__strtok(char *output, char *src, char *delimit)
|
||||||
|
{
|
||||||
|
return strtok_raw(output, src, delimit, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *git__strtok_keep(char *output, char *src, char *delimit)
|
||||||
|
{
|
||||||
|
return strtok_raw(output, src, delimit, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void git__hexdump(const char *buffer, size_t len)
|
void git__hexdump(const char *buffer, size_t len)
|
||||||
|
55
src/util.h
55
src/util.h
@ -19,8 +19,44 @@ extern int git__fmt(char *, size_t, const char *, ...)
|
|||||||
extern int git__prefixcmp(const char *str, const char *prefix);
|
extern int git__prefixcmp(const char *str, const char *prefix);
|
||||||
extern int git__suffixcmp(const char *str, const char *suffix);
|
extern int git__suffixcmp(const char *str, const char *suffix);
|
||||||
|
|
||||||
extern int git__dirname(char *dir, size_t n, char *path);
|
/*
|
||||||
extern int git__basename(char *base, size_t n, char *path);
|
* The dirname() function shall take a pointer to a character string
|
||||||
|
* that contains a pathname, and return a pointer to a string that is a
|
||||||
|
* pathname of the parent directory of that file. Trailing '/' characters
|
||||||
|
* in the path are not counted as part of the path.
|
||||||
|
*
|
||||||
|
* If path does not contain a '/', then dirname() shall return a pointer to
|
||||||
|
* the string ".". If path is a null pointer or points to an empty string,
|
||||||
|
* dirname() shall return a pointer to the string "." .
|
||||||
|
*
|
||||||
|
* The `git__dirname` implementation is thread safe. The returned
|
||||||
|
* string must be manually free'd.
|
||||||
|
*
|
||||||
|
* The `git__dirname_r` implementation expects a string allocated
|
||||||
|
* by the user with big enough size.
|
||||||
|
*/
|
||||||
|
extern char *git__dirname(const char *path);
|
||||||
|
extern int git__dirname_r(char *buffer, size_t bufflen, const char *path);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This function returns the basename of the file, which is the last
|
||||||
|
* part of its full name given by fname, with the drive letter and
|
||||||
|
* leading directories stripped off. For example, the basename of
|
||||||
|
* c:/foo/bar/file.ext is file.ext, and the basename of a:foo is foo.
|
||||||
|
*
|
||||||
|
* Trailing slashes and backslashes are significant: the basename of
|
||||||
|
* c:/foo/bar/ is an empty string after the rightmost slash.
|
||||||
|
*
|
||||||
|
* The `git__basename` implementation is thread safe. The returned
|
||||||
|
* string must be manually free'd.
|
||||||
|
*
|
||||||
|
* The `git__basename_r` implementation expects a string allocated
|
||||||
|
* by the user with big enough size.
|
||||||
|
*/
|
||||||
|
extern char *git__basename(const char *path);
|
||||||
|
extern int git__basename_r(char *buffer, size_t bufflen, const char *path);
|
||||||
|
|
||||||
|
extern const char *git__topdir(const char *path);
|
||||||
|
|
||||||
extern void git__hexdump(const char *buffer, size_t n);
|
extern void git__hexdump(const char *buffer, size_t n);
|
||||||
extern uint32_t git__hash(const void *key, int len, uint32_t seed);
|
extern uint32_t git__hash(const void *key, int len, uint32_t seed);
|
||||||
@ -40,6 +76,21 @@ GIT_INLINE(int) git__is_sizet(git_off_t p)
|
|||||||
# define git__rotl(v, s) (uint32_t)(((uint32_t)(v) << (s)) | ((uint32_t)(v) >> (32 - (s))))
|
# define git__rotl(v, s) (uint32_t)(((uint32_t)(v) << (s)) | ((uint32_t)(v) >> (32 - (s))))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
enum git_splitpath_flags
|
||||||
|
{
|
||||||
|
GIT_SPL_PATH = 1,
|
||||||
|
GIT_SPL_FILE = 2,
|
||||||
|
GIT_SPL_EXT = 4,
|
||||||
|
GIT_SPL_PATH_FILE = GIT_SPL_PATH + GIT_SPL_FILE,
|
||||||
|
GIT_SPL_FILE_EXT = GIT_SPL_FILE + GIT_SPL_EXT,
|
||||||
|
GIT_SPL_EXT_NO_PERIOD = 8,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
extern char *git__splitpath(char *path, int flag);
|
||||||
|
extern char *git__strtok(char *output, char *src, char *delimit);
|
||||||
|
extern char *git__strtok_keep(char *output, char *src, char *delimit);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Realloc the buffer pointed at by variable 'x' so that it can hold
|
* Realloc the buffer pointed at by variable 'x' so that it can hold
|
||||||
* at least 'nr' entries; the number of entries currently allocated
|
* at least 'nr' entries; the number of entries currently allocated
|
||||||
|
@ -61,61 +61,76 @@ BEGIN_TEST("strutil", suffix_comparison)
|
|||||||
END_TEST
|
END_TEST
|
||||||
|
|
||||||
BEGIN_TEST("strutil", dirname)
|
BEGIN_TEST("strutil", dirname)
|
||||||
char dir[64];
|
char dir[64], *dir2;
|
||||||
|
|
||||||
must_be_true(!(git__dirname(dir, sizeof(dir), NULL) < 0));
|
#define DIRNAME_TEST(A, B) { \
|
||||||
must_be_true(!strcmp(dir, "."));
|
must_be_true(git__dirname_r(dir, sizeof(dir), A) >= 0); \
|
||||||
|
must_be_true(strcmp(dir, B) == 0); \
|
||||||
|
must_be_true((dir2 = git__dirname(A)) != NULL); \
|
||||||
|
must_be_true(strcmp(dir2, B) == 0); \
|
||||||
|
free(dir2); \
|
||||||
|
}
|
||||||
|
|
||||||
must_be_true(!(git__dirname(dir, sizeof(dir), "") < 0));
|
DIRNAME_TEST(NULL, ".");
|
||||||
must_be_true(!strcmp(dir, "."));
|
DIRNAME_TEST("", ".");
|
||||||
|
DIRNAME_TEST("a", ".");
|
||||||
|
DIRNAME_TEST("/", "/");
|
||||||
|
DIRNAME_TEST("/usr", "/");
|
||||||
|
DIRNAME_TEST("/usr/", "/");
|
||||||
|
DIRNAME_TEST("/usr/lib", "/usr");
|
||||||
|
DIRNAME_TEST("usr/lib", "usr");
|
||||||
|
DIRNAME_TEST(".git/", ".");
|
||||||
|
|
||||||
must_be_true(!(git__dirname(dir, sizeof(dir), "a") < 0));
|
#undef DIRNAME_TEST
|
||||||
must_be_true(!strcmp(dir, "."));
|
|
||||||
|
|
||||||
must_be_true(!(git__dirname(dir, sizeof(dir), "/") < 0));
|
|
||||||
must_be_true(!strcmp(dir, "/"));
|
|
||||||
|
|
||||||
must_be_true(!(git__dirname(dir, sizeof(dir), "/usr") < 0));
|
|
||||||
must_be_true(!strcmp(dir, "/"));
|
|
||||||
|
|
||||||
/* TODO: should this be "/" instead (ie strip trailing / first) */
|
|
||||||
must_be_true(!(git__dirname(dir, sizeof(dir), "/usr/") < 0));
|
|
||||||
must_be_true(!strcmp(dir, "/usr"));
|
|
||||||
|
|
||||||
must_be_true(!(git__dirname(dir, sizeof(dir), "/usr/lib") < 0));
|
|
||||||
must_be_true(!strcmp(dir, "/usr"));
|
|
||||||
|
|
||||||
must_be_true(!(git__dirname(dir, sizeof(dir), "usr/lib") < 0));
|
|
||||||
must_be_true(!strcmp(dir, "usr"));
|
|
||||||
END_TEST
|
END_TEST
|
||||||
|
|
||||||
BEGIN_TEST("strutil", basename)
|
BEGIN_TEST("strutil", basename)
|
||||||
char base[64];
|
char base[64], *base2;
|
||||||
|
|
||||||
must_be_true(!(git__basename(base, sizeof(base), NULL) < 0));
|
#define BASENAME_TEST(A, B) { \
|
||||||
must_be_true(!strcmp(base, "."));
|
must_be_true(git__basename_r(base, sizeof(base), A) >= 0); \
|
||||||
|
must_be_true(strcmp(base, B) == 0); \
|
||||||
|
must_be_true((base2 = git__basename(A)) != NULL); \
|
||||||
|
must_be_true(strcmp(base2, B) == 0); \
|
||||||
|
free(base2); \
|
||||||
|
}
|
||||||
|
|
||||||
must_be_true(!(git__basename(base, sizeof(base), "") < 0));
|
BASENAME_TEST(NULL, ".");
|
||||||
must_be_true(!strcmp(base, "."));
|
BASENAME_TEST("", ".");
|
||||||
|
BASENAME_TEST("a", "a");
|
||||||
|
BASENAME_TEST("/", "/");
|
||||||
|
BASENAME_TEST("/usr", "usr");
|
||||||
|
BASENAME_TEST("/usr/", "usr");
|
||||||
|
BASENAME_TEST("/usr/lib", "lib");
|
||||||
|
BASENAME_TEST("usr/lib", "lib");
|
||||||
|
|
||||||
must_be_true(!(git__basename(base, sizeof(base), "a") < 0));
|
#undef BASENAME_TEST
|
||||||
must_be_true(!strcmp(base, "a"));
|
|
||||||
|
|
||||||
must_be_true(!(git__basename(base, sizeof(base), "/") < 0));
|
END_TEST
|
||||||
must_be_true(!strcmp(base, "/"));
|
|
||||||
|
|
||||||
must_be_true(!(git__basename(base, sizeof(base), "/usr") < 0));
|
BEGIN_TEST("strutil", topdir)
|
||||||
must_be_true(!strcmp(base, "usr"));
|
const char *dir;
|
||||||
|
|
||||||
/* TODO: should this be "usr" instead (ie strip trailing / first) */
|
#define TOPDIR_TEST(A, B) { \
|
||||||
must_be_true(!(git__basename(base, sizeof(base), "/usr/") < 0));
|
must_be_true((dir = git__topdir(A)) != NULL); \
|
||||||
must_be_true(!strcmp(base, ""));
|
must_be_true(strcmp(dir, B) == 0); \
|
||||||
|
}
|
||||||
|
|
||||||
must_be_true(!(git__basename(base, sizeof(base), "/usr/lib") < 0));
|
TOPDIR_TEST(".git/", ".git/");
|
||||||
must_be_true(!strcmp(base, "lib"));
|
TOPDIR_TEST("/.git/", ".git/");
|
||||||
|
TOPDIR_TEST("usr/local/.git/", ".git/");
|
||||||
|
TOPDIR_TEST("./.git/", ".git/");
|
||||||
|
TOPDIR_TEST("/usr/.git/", ".git/");
|
||||||
|
TOPDIR_TEST("/", "/");
|
||||||
|
TOPDIR_TEST("a/", "a/");
|
||||||
|
|
||||||
must_be_true(!(git__basename(base, sizeof(base), "usr/lib") < 0));
|
must_be_true(git__topdir("/usr/.git") == NULL);
|
||||||
must_be_true(!strcmp(base, "lib"));
|
must_be_true(git__topdir(".") == NULL);
|
||||||
|
must_be_true(git__topdir("") == NULL);
|
||||||
|
must_be_true(git__topdir("a") == NULL);
|
||||||
|
|
||||||
|
#undef TOPDIR_TEST
|
||||||
END_TEST
|
END_TEST
|
||||||
|
|
||||||
/* Initial size of 1 will cause writing past array bounds prior to fix */
|
/* Initial size of 1 will cause writing past array bounds prior to fix */
|
||||||
@ -579,6 +594,7 @@ git_testsuite *libgit2_suite_core(void)
|
|||||||
ADD_TEST(suite, "strutil", suffix_comparison);
|
ADD_TEST(suite, "strutil", suffix_comparison);
|
||||||
ADD_TEST(suite, "strutil", dirname);
|
ADD_TEST(suite, "strutil", dirname);
|
||||||
ADD_TEST(suite, "strutil", basename);
|
ADD_TEST(suite, "strutil", basename);
|
||||||
|
ADD_TEST(suite, "strutil", topdir);
|
||||||
|
|
||||||
ADD_TEST(suite, "vector", initial_size_one);
|
ADD_TEST(suite, "vector", initial_size_one);
|
||||||
ADD_TEST(suite, "vector", remove);
|
ADD_TEST(suite, "vector", remove);
|
||||||
|
Loading…
Reference in New Issue
Block a user