mirror of
https://github.com/checkpoint-restore/criu
synced 2025-09-02 07:15:31 +00:00
Use absolute path for image files
Now I try to restore CWD and a relative path will be invalid. Add new options -D to set image files directory. Signed-off-by: Andrey Vagin <avagin@openvz.org> Acked-by: Pavel Emelyanov <xemul@parallels.com> Signed-off-by: Cyrill Gorcunov <gorcunov@openvz.org>
This commit is contained in:
committed by
Cyrill Gorcunov
parent
a5439d5fed
commit
d261cf7958
30
cr-restore.c
30
cr-restore.c
@@ -295,7 +295,7 @@ static int prepare_shmem_pid(int pid)
|
|||||||
int sh_fd;
|
int sh_fd;
|
||||||
u32 type = 0;
|
u32 type = 0;
|
||||||
|
|
||||||
sh_fd = open_fmt_ro(FMT_FNAME_SHMEM, pid);
|
sh_fd = open_image_ro(FMT_FNAME_SHMEM, pid);
|
||||||
if (sh_fd < 0) {
|
if (sh_fd < 0) {
|
||||||
pr_perror("%d: Can't open shmem info\n", pid);
|
pr_perror("%d: Can't open shmem info\n", pid);
|
||||||
return 1;
|
return 1;
|
||||||
@@ -333,7 +333,7 @@ static int prepare_pipes_pid(int pid)
|
|||||||
int p_fd;
|
int p_fd;
|
||||||
u32 type = 0;
|
u32 type = 0;
|
||||||
|
|
||||||
p_fd = open_fmt_ro(FMT_FNAME_PIPES, pid);
|
p_fd = open_image_ro(FMT_FNAME_PIPES, pid);
|
||||||
if (p_fd < 0) {
|
if (p_fd < 0) {
|
||||||
pr_perror("%d: Can't open pipes image\n", pid);
|
pr_perror("%d: Can't open pipes image\n", pid);
|
||||||
return 1;
|
return 1;
|
||||||
@@ -514,7 +514,7 @@ static int prepare_fds(int pid)
|
|||||||
|
|
||||||
pr_info("%d: Opening files img\n", pid);
|
pr_info("%d: Opening files img\n", pid);
|
||||||
|
|
||||||
fdinfo_fd = open_fmt_ro(FMT_FNAME_FDINFO, pid);
|
fdinfo_fd = open_image_ro(FMT_FNAME_FDINFO, pid);
|
||||||
if (fdinfo_fd < 0) {
|
if (fdinfo_fd < 0) {
|
||||||
pr_perror("Can't open %d fdinfo", pid);
|
pr_perror("Can't open %d fdinfo", pid);
|
||||||
return 1;
|
return 1;
|
||||||
@@ -593,7 +593,7 @@ static int prepare_shmem(int pid)
|
|||||||
int sh_fd;
|
int sh_fd;
|
||||||
u32 type = 0;
|
u32 type = 0;
|
||||||
|
|
||||||
sh_fd = open_fmt_ro(FMT_FNAME_SHMEM, pid);
|
sh_fd = open_image_ro(FMT_FNAME_SHMEM, pid);
|
||||||
if (sh_fd < 0) {
|
if (sh_fd < 0) {
|
||||||
pr_perror("%d: Can't open shmem info\n", pid);
|
pr_perror("%d: Can't open shmem info\n", pid);
|
||||||
return 1;
|
return 1;
|
||||||
@@ -754,7 +754,7 @@ static int fixup_pages_data(int pid, int fd)
|
|||||||
|
|
||||||
pr_info("%d: Reading shmem pages img\n", pid);
|
pr_info("%d: Reading shmem pages img\n", pid);
|
||||||
|
|
||||||
shfd = open_fmt_ro(FMT_FNAME_PAGES_SHMEM, pid);
|
shfd = open_image_ro(FMT_FNAME_PAGES_SHMEM, pid);
|
||||||
if (shfd < 0) {
|
if (shfd < 0) {
|
||||||
pr_perror("Can't open %d shmem image %s\n", pid);
|
pr_perror("Can't open %d shmem image %s\n", pid);
|
||||||
return 1;
|
return 1;
|
||||||
@@ -836,7 +836,7 @@ static int prepare_and_sigreturn(int pid)
|
|||||||
int fd, fd_new;
|
int fd, fd_new;
|
||||||
struct stat buf;
|
struct stat buf;
|
||||||
|
|
||||||
fd = open_fmt_ro(FMT_FNAME_CORE, pid);
|
fd = open_image_ro(FMT_FNAME_CORE, pid);
|
||||||
if (fd < 0) {
|
if (fd < 0) {
|
||||||
pr_perror("%d: Can't open exec image\n", pid);
|
pr_perror("%d: Can't open exec image\n", pid);
|
||||||
return 1;
|
return 1;
|
||||||
@@ -846,7 +846,7 @@ static int prepare_and_sigreturn(int pid)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
sprintf(path, FMT_FNAME_CORE_OUT, pid);
|
IMAGE_PATH(path, FMT_FNAME_CORE_OUT, pid);
|
||||||
unlink(path);
|
unlink(path);
|
||||||
|
|
||||||
fd_new = open(path, O_RDWR | O_CREAT | O_EXCL, CR_FD_PERM);
|
fd_new = open(path, O_RDWR | O_CREAT | O_EXCL, CR_FD_PERM);
|
||||||
@@ -1058,7 +1058,7 @@ static int prepare_sigactions(int pid)
|
|||||||
u32 type = 0;
|
u32 type = 0;
|
||||||
int sig, i;
|
int sig, i;
|
||||||
|
|
||||||
fd_sigact = open_fmt_ro(FMT_FNAME_SIGACTS, pid);
|
fd_sigact = open_image_ro(FMT_FNAME_SIGACTS, pid);
|
||||||
if (fd_sigact < 0) {
|
if (fd_sigact < 0) {
|
||||||
pr_perror("%d: Can't open sigactions img\n", pid);
|
pr_perror("%d: Can't open sigactions img\n", pid);
|
||||||
return 1;
|
return 1;
|
||||||
@@ -1111,7 +1111,7 @@ static int prepare_pipes(int pid)
|
|||||||
|
|
||||||
pr_info("%d: Opening pipes\n", pid);
|
pr_info("%d: Opening pipes\n", pid);
|
||||||
|
|
||||||
pipes_fd = open_fmt_ro(FMT_FNAME_PIPES, pid);
|
pipes_fd = open_image_ro(FMT_FNAME_PIPES, pid);
|
||||||
if (pipes_fd < 0) {
|
if (pipes_fd < 0) {
|
||||||
pr_perror("%d: Can't open pipes img\n", pid);
|
pr_perror("%d: Can't open pipes img\n", pid);
|
||||||
return 1;
|
return 1;
|
||||||
@@ -1310,7 +1310,7 @@ static int restore_all_tasks(pid_t pid)
|
|||||||
int pstree_fd;
|
int pstree_fd;
|
||||||
u32 type = 0;
|
u32 type = 0;
|
||||||
|
|
||||||
sprintf(path, FMT_FNAME_PSTREE, pid);
|
IMAGE_PATH(path, FMT_FNAME_PSTREE, pid);
|
||||||
pstree_fd = open(path, O_RDONLY);
|
pstree_fd = open(path, O_RDONLY);
|
||||||
if (pstree_fd < 0) {
|
if (pstree_fd < 0) {
|
||||||
pr_perror("%d: Can't open pstree image\n", pid);
|
pr_perror("%d: Can't open pstree image\n", pid);
|
||||||
@@ -1349,7 +1349,7 @@ static long restorer_get_vma_hint(pid_t pid, struct list_head *self_vma_list, lo
|
|||||||
* better to stick with it.
|
* better to stick with it.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
snprintf(path, sizeof(path), FMT_FNAME_CORE, pid);
|
IMAGE_PATH(path, FMT_FNAME_CORE, pid);
|
||||||
fd = open(path, O_RDONLY, CR_FD_PERM);
|
fd = open(path, O_RDONLY, CR_FD_PERM);
|
||||||
if (fd < 0) {
|
if (fd < 0) {
|
||||||
pr_perror("Can't open %s\n", path);
|
pr_perror("Can't open %s\n", path);
|
||||||
@@ -1431,21 +1431,21 @@ static void sigreturn_restore(pid_t pstree_pid, pid_t pid)
|
|||||||
BUILD_BUG_ON(sizeof(struct task_restore_core_args) & 1);
|
BUILD_BUG_ON(sizeof(struct task_restore_core_args) & 1);
|
||||||
BUILD_BUG_ON(sizeof(struct thread_restore_args) & 1);
|
BUILD_BUG_ON(sizeof(struct thread_restore_args) & 1);
|
||||||
|
|
||||||
snprintf(path, sizeof(path), FMT_FNAME_PSTREE, pstree_pid);
|
IMAGE_PATH(path, FMT_FNAME_PSTREE, pstree_pid);
|
||||||
fd_pstree = open(path, O_RDONLY, CR_FD_PERM);
|
fd_pstree = open(path, O_RDONLY, CR_FD_PERM);
|
||||||
if (fd_pstree < 0) {
|
if (fd_pstree < 0) {
|
||||||
pr_perror("Can't open %s\n", path);
|
pr_perror("Can't open %s\n", path);
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
snprintf(path, sizeof(path), FMT_FNAME_CORE_OUT, pid);
|
IMAGE_PATH(path, FMT_FNAME_CORE_OUT, pid);
|
||||||
fd_core = open(path, O_RDONLY, CR_FD_PERM);
|
fd_core = open(path, O_RDONLY, CR_FD_PERM);
|
||||||
if (fd_core < 0) {
|
if (fd_core < 0) {
|
||||||
pr_perror("Can't open %s\n", path);
|
pr_perror("Can't open %s\n", path);
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
snprintf(self_vmas_path, sizeof(self_vmas_path), FMT_FNAME_VMAS, getpid());
|
IMAGE_PATH(self_vmas_path, FMT_FNAME_VMAS, getpid());
|
||||||
unlink(self_vmas_path);
|
unlink(self_vmas_path);
|
||||||
fd_self_vmas = open(self_vmas_path, O_CREAT | O_RDWR, CR_FD_PERM);
|
fd_self_vmas = open(self_vmas_path, O_CREAT | O_RDWR, CR_FD_PERM);
|
||||||
if (fd_self_vmas < 0) {
|
if (fd_self_vmas < 0) {
|
||||||
@@ -1593,7 +1593,7 @@ static void sigreturn_restore(pid_t pstree_pid, pid_t pid)
|
|||||||
read_ptr_safe(fd_pstree, &thread_args[i].pid, err);
|
read_ptr_safe(fd_pstree, &thread_args[i].pid, err);
|
||||||
|
|
||||||
/* Core files are to be opened */
|
/* Core files are to be opened */
|
||||||
snprintf(path, sizeof(path), FMT_FNAME_CORE, thread_args[i].pid);
|
IMAGE_PATH(path, FMT_FNAME_CORE, thread_args[i].pid);
|
||||||
thread_args[i].fd_core = open(path, O_RDONLY, CR_FD_PERM);
|
thread_args[i].fd_core = open(path, O_RDONLY, CR_FD_PERM);
|
||||||
if (thread_args[i].fd_core < 0) {
|
if (thread_args[i].fd_core < 0) {
|
||||||
pr_perror("Can't open %s\n", path);
|
pr_perror("Can't open %s\n", path);
|
||||||
|
36
crtools.c
36
crtools.c
@@ -91,6 +91,7 @@ struct cr_fdset *alloc_cr_fdset(pid_t pid)
|
|||||||
{
|
{
|
||||||
struct cr_fdset *cr_fdset;
|
struct cr_fdset *cr_fdset;
|
||||||
unsigned int i;
|
unsigned int i;
|
||||||
|
int ret;
|
||||||
|
|
||||||
cr_fdset = xzalloc(sizeof(*cr_fdset));
|
cr_fdset = xzalloc(sizeof(*cr_fdset));
|
||||||
if (!cr_fdset)
|
if (!cr_fdset)
|
||||||
@@ -98,10 +99,14 @@ struct cr_fdset *alloc_cr_fdset(pid_t pid)
|
|||||||
|
|
||||||
for (i = 0; i < CR_FD_MAX; i++) {
|
for (i = 0; i < CR_FD_MAX; i++) {
|
||||||
cr_fdset->desc[i].tmpl = &fdset_template[i];
|
cr_fdset->desc[i].tmpl = &fdset_template[i];
|
||||||
snprintf(cr_fdset->desc[i].name,
|
ret = get_image_path(cr_fdset->desc[i].name,
|
||||||
sizeof(cr_fdset->desc[i].name),
|
sizeof(cr_fdset->desc[i].name),
|
||||||
cr_fdset->desc[i].tmpl->fmt,
|
cr_fdset->desc[i].tmpl->fmt,
|
||||||
pid);
|
pid);
|
||||||
|
if (ret) {
|
||||||
|
xfree(cr_fdset);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
cr_fdset->desc[i].fd = -1;
|
cr_fdset->desc[i].fd = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,6 +231,22 @@ void free_cr_fdset(struct cr_fdset **cr_fdset)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char image_dir[PATH_MAX];
|
||||||
|
int get_image_path(char *path, int size, const char *fmt, int pid)
|
||||||
|
{
|
||||||
|
int image_dir_size = strlen(image_dir);
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
strcpy(path, image_dir);
|
||||||
|
path[image_dir_size] = '/';
|
||||||
|
ret = snprintf(path + image_dir_size + 1, size, fmt, pid);
|
||||||
|
if (ret == -1 || ret > size) {
|
||||||
|
pr_err("can't get image path");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
pid_t pid;
|
pid_t pid;
|
||||||
@@ -233,7 +254,7 @@ int main(int argc, char *argv[])
|
|||||||
int opt, idx;
|
int opt, idx;
|
||||||
int action = -1;
|
int action = -1;
|
||||||
|
|
||||||
static const char short_opts[] = "drskf:p:t:hc";
|
static const char short_opts[] = "drskf:p:t:hcD:";
|
||||||
static const struct option long_opts[] = {
|
static const struct option long_opts[] = {
|
||||||
{ "dump", no_argument, NULL, 'd' },
|
{ "dump", no_argument, NULL, 'd' },
|
||||||
{ "restore", no_argument, NULL, 'r' },
|
{ "restore", no_argument, NULL, 'r' },
|
||||||
@@ -284,12 +305,23 @@ int main(int argc, char *argv[])
|
|||||||
case 'k':
|
case 'k':
|
||||||
opts.final_state = CR_TASK_KILL;
|
opts.final_state = CR_TASK_KILL;
|
||||||
break;
|
break;
|
||||||
|
case 'D':
|
||||||
|
if (chdir(optarg)) {
|
||||||
|
pr_perror("can't change working directory");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'h':
|
case 'h':
|
||||||
default:
|
default:
|
||||||
goto usage;
|
goto usage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (getcwd(image_dir, sizeof(image_dir)) < 0) {
|
||||||
|
pr_perror("can't get currect directory\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'd':
|
case 'd':
|
||||||
ret = cr_dump_tasks(pid, &opts);
|
ret = cr_dump_tasks(pid, &opts);
|
||||||
|
@@ -58,6 +58,12 @@ struct cr_fd_desc_tmpl {
|
|||||||
#define FMT_FNAME_VMAS "vmas-%d.img"
|
#define FMT_FNAME_VMAS "vmas-%d.img"
|
||||||
#define FMT_FNAME_SIGACTS "sigacts-%d.img"
|
#define FMT_FNAME_SIGACTS "sigacts-%d.img"
|
||||||
|
|
||||||
|
extern int get_image_path(char *path, int size, const char *fmt, int pid);
|
||||||
|
#define IMAGE_PATH(path, fmt, pid) get_image_path(path, sizeof(path), fmt, pid);
|
||||||
|
|
||||||
|
extern char image_dir[];
|
||||||
|
#define open_image_ro(fmt, ...) open_fmt("%s/" fmt, O_RDONLY, image_dir, __VA_ARGS__)
|
||||||
|
|
||||||
#define LAST_PID_PATH "/proc/sys/kernel/ns_last_pid"
|
#define LAST_PID_PATH "/proc/sys/kernel/ns_last_pid"
|
||||||
#define LAST_PID_PERM 0666
|
#define LAST_PID_PERM 0666
|
||||||
|
|
||||||
|
@@ -174,8 +174,6 @@ DIR *opendir_proc(char *fmt, ...);
|
|||||||
FILE *fopen_proc(char *fmt, char *mode, ...);
|
FILE *fopen_proc(char *fmt, char *mode, ...);
|
||||||
int open_fmt(char *fmt, int mode, ...);
|
int open_fmt(char *fmt, int mode, ...);
|
||||||
|
|
||||||
#define open_fmt_ro(fmt, ...) open_fmt(fmt, O_RDONLY, __VA_ARGS__)
|
|
||||||
|
|
||||||
#define __xalloc(op, size, ...) \
|
#define __xalloc(op, size, ...) \
|
||||||
({ \
|
({ \
|
||||||
void *___p = op( __VA_ARGS__ ); \
|
void *___p = op( __VA_ARGS__ ); \
|
||||||
|
@@ -345,20 +345,12 @@ int parasite_dump_sigacts_seized(struct parasite_ctl *ctl, struct cr_fdset *cr_f
|
|||||||
parasite_args_cmd_dumpsigacts_t parasite_sigacts = { };
|
parasite_args_cmd_dumpsigacts_t parasite_sigacts = { };
|
||||||
|
|
||||||
int status, path_len, ret = -1;
|
int status, path_len, ret = -1;
|
||||||
char *cwd = NULL;
|
|
||||||
|
|
||||||
pr_info("\n");
|
pr_info("\n");
|
||||||
pr_info("Dumping sigactions (pid: %d)\n", ctl->pid);
|
pr_info("Dumping sigactions (pid: %d)\n", ctl->pid);
|
||||||
pr_info("----------------------------------------\n");
|
pr_info("----------------------------------------\n");
|
||||||
|
|
||||||
cwd = get_current_dir_name();
|
path_len = strlen(cr_fdset->desc[CR_FD_SIGACT].name);
|
||||||
if (!cwd) {
|
|
||||||
pr_err("No memory to obtain cwd\n");
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
path_len = strlen(cr_fdset->desc[CR_FD_SIGACT].name) +
|
|
||||||
strlen(cwd) + 2;
|
|
||||||
|
|
||||||
if (path_len > sizeof(parasite_sigacts.open_path)) {
|
if (path_len > sizeof(parasite_sigacts.open_path)) {
|
||||||
pr_panic("Dumping sigactions path is too long (%d while %d allowed)\n",
|
pr_panic("Dumping sigactions path is too long (%d while %d allowed)\n",
|
||||||
@@ -371,9 +363,9 @@ int parasite_dump_sigacts_seized(struct parasite_ctl *ctl, struct cr_fdset *cr_f
|
|||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
snprintf(parasite_sigacts.open_path,
|
strncpy(parasite_sigacts.open_path,
|
||||||
sizeof(parasite_sigacts.open_path),
|
cr_fdset->desc[CR_FD_SIGACT].name,
|
||||||
"%s/%s", cwd, cr_fdset->desc[CR_FD_SIGACT].name);
|
sizeof(parasite_sigacts.open_path));
|
||||||
|
|
||||||
parasite_sigacts.open_flags = O_WRONLY;
|
parasite_sigacts.open_flags = O_WRONLY;
|
||||||
parasite_sigacts.open_mode = CR_FD_PERM_DUMP;
|
parasite_sigacts.open_mode = CR_FD_PERM_DUMP;
|
||||||
@@ -385,7 +377,6 @@ int parasite_dump_sigacts_seized(struct parasite_ctl *ctl, struct cr_fdset *cr_f
|
|||||||
err:
|
err:
|
||||||
jerr(fchmod(cr_fdset->desc[CR_FD_SIGACT].fd, CR_FD_PERM), out);
|
jerr(fchmod(cr_fdset->desc[CR_FD_SIGACT].fd, CR_FD_PERM), out);
|
||||||
out:
|
out:
|
||||||
xfree(cwd);
|
|
||||||
pr_info("----------------------------------------\n");
|
pr_info("----------------------------------------\n");
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
@@ -406,20 +397,13 @@ int parasite_dump_pages_seized(struct parasite_ctl *ctl, struct list_head *vma_a
|
|||||||
struct vma_area *vma_area;
|
struct vma_area *vma_area;
|
||||||
siginfo_t siginfo;
|
siginfo_t siginfo;
|
||||||
int status, path_len, ret = -1;
|
int status, path_len, ret = -1;
|
||||||
char *cwd = NULL;
|
|
||||||
|
|
||||||
pr_info("\n");
|
pr_info("\n");
|
||||||
pr_info("Dumping pages (type: %d pid: %d)\n", fd_type, ctl->pid);
|
pr_info("Dumping pages (type: %d pid: %d)\n", fd_type, ctl->pid);
|
||||||
pr_info("----------------------------------------\n");
|
pr_info("----------------------------------------\n");
|
||||||
|
|
||||||
cwd = get_current_dir_name();
|
path_len = strlen(cr_fdset->desc[fd_type].name);
|
||||||
if (!cwd) {
|
pr_info("Dumping pages %s\n", cr_fdset->desc[fd_type].name);
|
||||||
pr_err("No memory to obtain cwd\n");
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
path_len = strlen(cr_fdset->desc[fd_type].name) +
|
|
||||||
strlen(cwd) + 2;
|
|
||||||
|
|
||||||
if (path_len > sizeof(parasite_dumppages.open_path)) {
|
if (path_len > sizeof(parasite_dumppages.open_path)) {
|
||||||
pr_panic("Dumping pages path is too long (%d while %d allowed)\n",
|
pr_panic("Dumping pages path is too long (%d while %d allowed)\n",
|
||||||
@@ -438,9 +422,9 @@ int parasite_dump_pages_seized(struct parasite_ctl *ctl, struct list_head *vma_a
|
|||||||
*/
|
*/
|
||||||
fsync(cr_fdset->desc[fd_type].fd);
|
fsync(cr_fdset->desc[fd_type].fd);
|
||||||
|
|
||||||
snprintf(parasite_dumppages.open_path,
|
strncpy(parasite_dumppages.open_path,
|
||||||
sizeof(parasite_dumppages.open_path),
|
cr_fdset->desc[fd_type].name,
|
||||||
"%s/%s", cwd, cr_fdset->desc[fd_type].name);
|
sizeof(parasite_dumppages.open_path));
|
||||||
|
|
||||||
parasite_dumppages.open_flags = O_WRONLY;
|
parasite_dumppages.open_flags = O_WRONLY;
|
||||||
parasite_dumppages.open_mode = CR_FD_PERM_DUMP;
|
parasite_dumppages.open_mode = CR_FD_PERM_DUMP;
|
||||||
@@ -511,7 +495,6 @@ err_restore:
|
|||||||
jerr(fchmod(cr_fdset->desc[fd_type].fd, CR_FD_PERM), out);
|
jerr(fchmod(cr_fdset->desc[fd_type].fd, CR_FD_PERM), out);
|
||||||
|
|
||||||
out:
|
out:
|
||||||
xfree(cwd);
|
|
||||||
pr_info("----------------------------------------\n");
|
pr_info("----------------------------------------\n");
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
Reference in New Issue
Block a user