Merge branch '1500_segfault_on_bg_copy'
* 1500_segfault_on_bg_copy: Fixed problems: Ticket #1500: Segmentation fault while background copying
Этот коммит содержится в:
Коммит
9c854e73d4
@ -61,6 +61,9 @@ int we_are_background = 0;
|
|||||||
/* File descriptor for talking to our parent */
|
/* File descriptor for talking to our parent */
|
||||||
static int parent_fd;
|
static int parent_fd;
|
||||||
|
|
||||||
|
/* File descriptor for messages from our parent */
|
||||||
|
static int from_parent_fd;
|
||||||
|
|
||||||
#define MAXCALLARGS 4 /* Number of arguments supported */
|
#define MAXCALLARGS 4 /* Number of arguments supported */
|
||||||
|
|
||||||
struct TaskList *task_list = NULL;
|
struct TaskList *task_list = NULL;
|
||||||
@ -68,7 +71,8 @@ struct TaskList *task_list = NULL;
|
|||||||
static int background_attention (int fd, void *closure);
|
static int background_attention (int fd, void *closure);
|
||||||
|
|
||||||
static void
|
static void
|
||||||
register_task_running (FileOpContext *ctx, pid_t pid, int fd, char *info)
|
register_task_running (FileOpContext *ctx, pid_t pid, int fd, int to_child,
|
||||||
|
char *info)
|
||||||
{
|
{
|
||||||
TaskList *new;
|
TaskList *new;
|
||||||
|
|
||||||
@ -78,13 +82,14 @@ register_task_running (FileOpContext *ctx, pid_t pid, int fd, char *info)
|
|||||||
new->state = Task_Running;
|
new->state = Task_Running;
|
||||||
new->next = task_list;
|
new->next = task_list;
|
||||||
new->fd = fd;
|
new->fd = fd;
|
||||||
|
new->to_child_fd = to_child;
|
||||||
task_list = new;
|
task_list = new;
|
||||||
|
|
||||||
add_select_channel (fd, background_attention, ctx);
|
add_select_channel (fd, background_attention, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
int
|
||||||
unregister_task_running (pid_t pid, int fd)
|
destroy_task_and_return_fd (pid_t pid)
|
||||||
{
|
{
|
||||||
TaskList *p = task_list;
|
TaskList *p = task_list;
|
||||||
TaskList *prev = 0;
|
TaskList *prev = 0;
|
||||||
@ -97,11 +102,28 @@ unregister_task_running (pid_t pid, int fd)
|
|||||||
task_list = p->next;
|
task_list = p->next;
|
||||||
g_free (p->info);
|
g_free (p->info);
|
||||||
g_free (p);
|
g_free (p);
|
||||||
break;
|
return p->fd;
|
||||||
}
|
}
|
||||||
prev = p;
|
prev = p;
|
||||||
p = p->next;
|
p = p->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* pid not found */
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
unregister_task_running (pid_t pid, int fd)
|
||||||
|
{
|
||||||
|
destroy_task_and_return_fd(pid);
|
||||||
|
delete_select_channel (fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
unregister_task_with_pid (pid_t pid)
|
||||||
|
{
|
||||||
|
int fd = destroy_task_and_return_fd(pid);
|
||||||
|
if (fd != -1)
|
||||||
delete_select_channel (fd);
|
delete_select_channel (fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,15 +139,21 @@ int
|
|||||||
do_background (struct FileOpContext *ctx, char *info)
|
do_background (struct FileOpContext *ctx, char *info)
|
||||||
{
|
{
|
||||||
int comm[2]; /* control connection stream */
|
int comm[2]; /* control connection stream */
|
||||||
|
int back_comm[2]; /* back connection */
|
||||||
pid_t pid;
|
pid_t pid;
|
||||||
|
|
||||||
if (pipe (comm) == -1)
|
if (pipe (comm) == -1)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
if (pipe (back_comm) == -1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
if ((pid = fork ()) == -1) {
|
if ((pid = fork ()) == -1) {
|
||||||
int saved_errno = errno;
|
int saved_errno = errno;
|
||||||
(void) close (comm[0]);
|
(void) close (comm[0]);
|
||||||
(void) close (comm[1]);
|
(void) close (comm[1]);
|
||||||
|
(void) close (back_comm[0]);
|
||||||
|
(void) close (back_comm[1]);
|
||||||
errno = saved_errno;
|
errno = saved_errno;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -133,8 +161,9 @@ do_background (struct FileOpContext *ctx, char *info)
|
|||||||
if (pid == 0) {
|
if (pid == 0) {
|
||||||
int nullfd;
|
int nullfd;
|
||||||
|
|
||||||
close (comm[0]);
|
|
||||||
parent_fd = comm[1];
|
parent_fd = comm[1];
|
||||||
|
from_parent_fd = back_comm[0];
|
||||||
|
|
||||||
we_are_background = 1;
|
we_are_background = 1;
|
||||||
current_dlg = NULL;
|
current_dlg = NULL;
|
||||||
|
|
||||||
@ -151,9 +180,8 @@ do_background (struct FileOpContext *ctx, char *info)
|
|||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
close (comm[1]);
|
|
||||||
ctx->pid = pid;
|
ctx->pid = pid;
|
||||||
register_task_running (ctx, pid, comm[0], info);
|
register_task_running (ctx, pid, comm[0], back_comm[1], info);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -205,16 +233,19 @@ background_attention (int fd, void *closure)
|
|||||||
int have_ctx;
|
int have_ctx;
|
||||||
union
|
union
|
||||||
{
|
{
|
||||||
|
int (*have_ctx0)(int);
|
||||||
int (*have_ctx1)(int, char *);
|
int (*have_ctx1)(int, char *);
|
||||||
int (*have_ctx2)(int, char *, char *);
|
int (*have_ctx2)(int, char *, char *);
|
||||||
int (*have_ctx3)(int, char *, char *, char *);
|
int (*have_ctx3)(int, char *, char *, char *);
|
||||||
int (*have_ctx4)(int, char *, char *, char *, char *);
|
int (*have_ctx4)(int, char *, char *, char *, char *);
|
||||||
|
|
||||||
|
int (*non_have_ctx0)(FileOpContext *, int);
|
||||||
int (*non_have_ctx1)(FileOpContext *, int, char *);
|
int (*non_have_ctx1)(FileOpContext *, int, char *);
|
||||||
int (*non_have_ctx2)(FileOpContext *, int, char *, char *);
|
int (*non_have_ctx2)(FileOpContext *, int, char *, char *);
|
||||||
int (*non_have_ctx3)(FileOpContext *, int, char *, char *, char *);
|
int (*non_have_ctx3)(FileOpContext *, int, char *, char *, char *);
|
||||||
int (*non_have_ctx4)(FileOpContext *, int, char *, char *, char *, char *);
|
int (*non_have_ctx4)(FileOpContext *, int, char *, char *, char *, char *);
|
||||||
|
|
||||||
|
char * (*ret_str0)();
|
||||||
char * (*ret_str1)(char *);
|
char * (*ret_str1)(char *);
|
||||||
char * (*ret_str2)(char *, char *);
|
char * (*ret_str2)(char *, char *);
|
||||||
char * (*ret_str3)(char *, char *, char *);
|
char * (*ret_str3)(char *, char *, char *);
|
||||||
@ -226,6 +257,8 @@ background_attention (int fd, void *closure)
|
|||||||
int argc, i, result, status;
|
int argc, i, result, status;
|
||||||
char *data [MAXCALLARGS];
|
char *data [MAXCALLARGS];
|
||||||
ssize_t bytes;
|
ssize_t bytes;
|
||||||
|
struct TaskList *p;
|
||||||
|
int to_child_fd = -1;
|
||||||
enum ReturnType type;
|
enum ReturnType type;
|
||||||
|
|
||||||
ctx = closure;
|
ctx = closure;
|
||||||
@ -272,10 +305,25 @@ background_attention (int fd, void *closure)
|
|||||||
data [i][size] = 0; /* NULL terminate the blocks (they could be strings) */
|
data [i][size] = 0; /* NULL terminate the blocks (they could be strings) */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Find child task info by descriptor */
|
||||||
|
/* Find before call, because process can destroy self after */
|
||||||
|
for (p = task_list; p; p = p->next) {
|
||||||
|
if (p->fd == fd)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p) to_child_fd = p->to_child_fd;
|
||||||
|
|
||||||
|
if (to_child_fd == -1)
|
||||||
|
message (D_ERROR, _(" Background process error "), _(" Unknown error in child "));
|
||||||
|
|
||||||
/* Handle the call */
|
/* Handle the call */
|
||||||
if (type == Return_Integer){
|
if (type == Return_Integer){
|
||||||
if (!have_ctx)
|
if (!have_ctx)
|
||||||
switch (argc){
|
switch (argc){
|
||||||
|
case 0:
|
||||||
|
result = routine.have_ctx0 (Background);
|
||||||
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
result = routine.have_ctx1 (Background, data [0]);
|
result = routine.have_ctx1 (Background, data [0]);
|
||||||
break;
|
break;
|
||||||
@ -291,6 +339,9 @@ background_attention (int fd, void *closure)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
switch (argc){
|
switch (argc){
|
||||||
|
case 0:
|
||||||
|
result = routine.non_have_ctx0 (ctx, Background);
|
||||||
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
result = routine.non_have_ctx1 (ctx, Background, data [0]);
|
result = routine.non_have_ctx1 (ctx, Background, data [0]);
|
||||||
break;
|
break;
|
||||||
@ -306,9 +357,9 @@ background_attention (int fd, void *closure)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Send the result code and the value for shared variables */
|
/* Send the result code and the value for shared variables */
|
||||||
write (fd, &result, sizeof (int));
|
write (to_child_fd, &result, sizeof (int));
|
||||||
if (have_ctx)
|
if (have_ctx && to_child_fd != -1)
|
||||||
write (fd, ctx, sizeof (FileOpContext));
|
write (to_child_fd, ctx, sizeof (FileOpContext));
|
||||||
} else if (type == Return_String) {
|
} else if (type == Return_String) {
|
||||||
int len;
|
int len;
|
||||||
char *resstr = NULL;
|
char *resstr = NULL;
|
||||||
@ -317,6 +368,9 @@ background_attention (int fd, void *closure)
|
|||||||
* parameter. Currently, this is not used here
|
* parameter. Currently, this is not used here
|
||||||
*/
|
*/
|
||||||
switch (argc){
|
switch (argc){
|
||||||
|
case 0:
|
||||||
|
resstr = routine.ret_str0 ();
|
||||||
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
resstr = routine.ret_str1 (data [0]);
|
resstr = routine.ret_str1 (data [0]);
|
||||||
break;
|
break;
|
||||||
@ -333,14 +387,14 @@ background_attention (int fd, void *closure)
|
|||||||
}
|
}
|
||||||
if (resstr){
|
if (resstr){
|
||||||
len = strlen (resstr);
|
len = strlen (resstr);
|
||||||
write (fd, &len, sizeof (len));
|
write (to_child_fd, &len, sizeof (len));
|
||||||
if (len){
|
if (len){
|
||||||
write (fd, resstr, len);
|
write (to_child_fd, resstr, len);
|
||||||
g_free (resstr);
|
g_free (resstr);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
len = 0;
|
len = 0;
|
||||||
write (fd, &len, sizeof (len));
|
write (to_child_fd, &len, sizeof (len));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (i = 0; i < argc; i++)
|
for (i = 0; i < argc; i++)
|
||||||
@ -392,9 +446,10 @@ parent_call (void *routine, struct FileOpContext *ctx, int argc, ...)
|
|||||||
write (parent_fd, &len, sizeof (int));
|
write (parent_fd, &len, sizeof (int));
|
||||||
write (parent_fd, value, len);
|
write (parent_fd, value, len);
|
||||||
}
|
}
|
||||||
read (parent_fd, &i, sizeof (int));
|
|
||||||
|
read (from_parent_fd, &i, sizeof (int));
|
||||||
if (ctx)
|
if (ctx)
|
||||||
read (parent_fd, ctx, sizeof (FileOpContext));
|
read (from_parent_fd, ctx, sizeof (FileOpContext));
|
||||||
|
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
@ -417,11 +472,11 @@ parent_call_string (void *routine, int argc, ...)
|
|||||||
write (parent_fd, &len, sizeof (int));
|
write (parent_fd, &len, sizeof (int));
|
||||||
write (parent_fd, value, len);
|
write (parent_fd, value, len);
|
||||||
}
|
}
|
||||||
read (parent_fd, &i, sizeof (int));
|
read (from_parent_fd, &i, sizeof (int));
|
||||||
if (!i)
|
if (!i)
|
||||||
return NULL;
|
return NULL;
|
||||||
str = g_malloc (i + 1);
|
str = g_malloc (i + 1);
|
||||||
read (parent_fd, str, i);
|
read (from_parent_fd, str, i);
|
||||||
str [i] = 0;
|
str [i] = 0;
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ enum TaskState {
|
|||||||
|
|
||||||
typedef struct TaskList {
|
typedef struct TaskList {
|
||||||
int fd;
|
int fd;
|
||||||
|
int to_child_fd;
|
||||||
pid_t pid;
|
pid_t pid;
|
||||||
int state;
|
int state;
|
||||||
char *info;
|
char *info;
|
||||||
@ -31,6 +32,7 @@ int parent_call (void *routine, struct FileOpContext *ctx, int argc, ...);
|
|||||||
char *parent_call_string (void *routine, int argc, ...);
|
char *parent_call_string (void *routine, int argc, ...);
|
||||||
|
|
||||||
void unregister_task_running (pid_t pid, int fd);
|
void unregister_task_running (pid_t pid, int fd);
|
||||||
|
void unregister_task_with_pid (pid_t pid);
|
||||||
extern int we_are_background;
|
extern int we_are_background;
|
||||||
|
|
||||||
#endif /* !WITH_BACKGROUND */
|
#endif /* !WITH_BACKGROUND */
|
||||||
|
@ -883,7 +883,7 @@ task_cb (int action)
|
|||||||
sig = SIGKILL;
|
sig = SIGKILL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sig == SIGINT)
|
if (sig == SIGKILL)
|
||||||
unregister_task_running (tl->pid, tl->fd);
|
unregister_task_running (tl->pid, tl->fd);
|
||||||
|
|
||||||
kill (tl->pid, sig);
|
kill (tl->pid, sig);
|
||||||
|
63
src/file.c
63
src/file.c
@ -381,12 +381,18 @@ enum {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static FileProgressStatus
|
static FileProgressStatus
|
||||||
warn_same_file (const char *fmt, const char *a, const char *b)
|
real_warn_same_file (enum OperationMode mode, const char *fmt,
|
||||||
|
const char *a, const char *b)
|
||||||
{
|
{
|
||||||
char *msg;
|
char *msg;
|
||||||
int result = 0;
|
int result = 0;
|
||||||
|
const char *head_msg;
|
||||||
|
|
||||||
|
head_msg = mode == Foreground ? MSG_ERROR :
|
||||||
|
_(" Background process error ");
|
||||||
|
|
||||||
msg = g_strdup_printf (fmt, a, b);
|
msg = g_strdup_printf (fmt, a, b);
|
||||||
result = query_dialog (MSG_ERROR, msg, D_ERROR, 2, _("&Skip"), _("&Abort"));
|
result = query_dialog (head_msg, msg, D_ERROR, 2, _("&Skip"), _("&Abort"));
|
||||||
g_free(msg);
|
g_free(msg);
|
||||||
do_refresh ();
|
do_refresh ();
|
||||||
if ( result ) { /* 1 == Abort */
|
if ( result ) { /* 1 == Abort */
|
||||||
@ -396,6 +402,31 @@ warn_same_file (const char *fmt, const char *a, const char *b)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef WITH_BACKGROUND
|
||||||
|
static FileProgressStatus
|
||||||
|
warn_same_file (const char *fmt, const char *a, const char *b)
|
||||||
|
{
|
||||||
|
union {
|
||||||
|
void *p;
|
||||||
|
FileProgressStatus (*f) (enum OperationMode, const char *fmt,
|
||||||
|
const char *a, const char *b);
|
||||||
|
} pntr;
|
||||||
|
pntr.f = real_warn_same_file;
|
||||||
|
|
||||||
|
if (we_are_background)
|
||||||
|
return parent_call (pntr.p, NULL, 3, strlen (fmt),
|
||||||
|
fmt, strlen(a), a, strlen(b), b);
|
||||||
|
else
|
||||||
|
return real_warn_same_file (Foreground, fmt, a, b);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
static FileProgressStatus
|
||||||
|
warn_same_file (const char *fmt, const char *a, const char *b)
|
||||||
|
{
|
||||||
|
return real_warn_same_file (Foreground, fmt, a, b);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
FileProgressStatus
|
FileProgressStatus
|
||||||
copy_file_file (FileOpContext *ctx, const char *src_path, const char *dst_path,
|
copy_file_file (FileOpContext *ctx, const char *src_path, const char *dst_path,
|
||||||
int ask_overwrite, off_t *progress_count,
|
int ask_overwrite, off_t *progress_count,
|
||||||
@ -1756,6 +1787,17 @@ panel_operate_generate_prompt (const WPanel *panel, const int operation,
|
|||||||
return g_strdup (format_string);
|
return g_strdup (format_string);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef WITH_BACKGROUND
|
||||||
|
int end_bg_process (FileOpContext *ctx, enum OperationMode mode) {
|
||||||
|
int pid = ctx->pid;
|
||||||
|
ctx->pid = 0;
|
||||||
|
|
||||||
|
unregister_task_with_pid(pid);
|
||||||
|
// file_op_context_destroy(ctx);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* panel_operate:
|
* panel_operate:
|
||||||
*
|
*
|
||||||
@ -1900,6 +1942,12 @@ panel_operate (void *source_panel, FileOperation operation,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Background also need ctx->ui, but not full */
|
||||||
|
if (do_bg)
|
||||||
|
file_op_context_create_ui_without_init (ctx, 1);
|
||||||
|
else
|
||||||
|
file_op_context_create_ui (ctx, 1);
|
||||||
|
|
||||||
#ifdef WITH_BACKGROUND
|
#ifdef WITH_BACKGROUND
|
||||||
/* Did the user select to do a background operation? */
|
/* Did the user select to do a background operation? */
|
||||||
if (do_bg) {
|
if (do_bg) {
|
||||||
@ -1938,11 +1986,6 @@ panel_operate (void *source_panel, FileOperation operation,
|
|||||||
|
|
||||||
/* Now, let's do the job */
|
/* Now, let's do the job */
|
||||||
|
|
||||||
if (do_bg)
|
|
||||||
ctx->ui = NULL;
|
|
||||||
else
|
|
||||||
file_op_context_create_ui (ctx, 1);
|
|
||||||
|
|
||||||
/* This code is only called by the tree and panel code */
|
/* This code is only called by the tree and panel code */
|
||||||
if (single_entry) {
|
if (single_entry) {
|
||||||
/* We now have ETA in all cases */
|
/* We now have ETA in all cases */
|
||||||
@ -2170,6 +2213,12 @@ panel_operate (void *source_panel, FileOperation operation,
|
|||||||
#ifdef WITH_BACKGROUND
|
#ifdef WITH_BACKGROUND
|
||||||
/* Let our parent know we are saying bye bye */
|
/* Let our parent know we are saying bye bye */
|
||||||
if (we_are_background) {
|
if (we_are_background) {
|
||||||
|
int cur_pid = getpid();
|
||||||
|
/* Send pid to parent with child context, it is fork and
|
||||||
|
don't modify real parent ctx */
|
||||||
|
ctx->pid = cur_pid;
|
||||||
|
parent_call ((void *) end_bg_process, ctx, 0);
|
||||||
|
|
||||||
vfs_shut ();
|
vfs_shut ();
|
||||||
_exit (0);
|
_exit (0);
|
||||||
}
|
}
|
||||||
|
@ -250,7 +250,7 @@ check_progress_buttons (FileOpContext *ctx)
|
|||||||
/* {{{ File progress display routines */
|
/* {{{ File progress display routines */
|
||||||
|
|
||||||
void
|
void
|
||||||
file_op_context_create_ui (FileOpContext *ctx, int with_eta)
|
file_op_context_create_ui_without_init (FileOpContext *ctx, int with_eta)
|
||||||
{
|
{
|
||||||
FileOpContextUI *ui;
|
FileOpContextUI *ui;
|
||||||
int x_size;
|
int x_size;
|
||||||
@ -321,6 +321,18 @@ file_op_context_create_ui (FileOpContext *ctx, int with_eta)
|
|||||||
label_new (3, FCOPY_GAUGE_X, sixty));
|
label_new (3, FCOPY_GAUGE_X, sixty));
|
||||||
add_widget (ui->op_dlg, ui->file_label[0] =
|
add_widget (ui->op_dlg, ui->file_label[0] =
|
||||||
label_new (3, FCOPY_LABEL_X, fifteen));
|
label_new (3, FCOPY_LABEL_X, fifteen));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
file_op_context_create_ui (FileOpContext *ctx, int with_eta)
|
||||||
|
{
|
||||||
|
FileOpContextUI *ui;
|
||||||
|
|
||||||
|
g_return_if_fail (ctx != NULL);
|
||||||
|
g_return_if_fail (ctx->ui == NULL);
|
||||||
|
|
||||||
|
file_op_context_create_ui_without_init(ctx, with_eta);
|
||||||
|
ui = ctx->ui;
|
||||||
|
|
||||||
/* We will manage the dialog without any help, that's why
|
/* We will manage the dialog without any help, that's why
|
||||||
we have to call init_dlg */
|
we have to call init_dlg */
|
||||||
|
@ -144,6 +144,7 @@ enum OperationMode {
|
|||||||
/* The following functions are implemented separately by each port */
|
/* The following functions are implemented separately by each port */
|
||||||
|
|
||||||
void file_op_context_create_ui (FileOpContext *ctx, int with_eta);
|
void file_op_context_create_ui (FileOpContext *ctx, int with_eta);
|
||||||
|
void file_op_context_create_ui_without_init (FileOpContext *ctx, int with_eta);
|
||||||
void file_op_context_destroy_ui (FileOpContext *ctx);
|
void file_op_context_destroy_ui (FileOpContext *ctx);
|
||||||
|
|
||||||
FileProgressStatus file_progress_show (FileOpContext *ctx, off_t done, off_t total);
|
FileProgressStatus file_progress_show (FileOpContext *ctx, off_t done, off_t total);
|
||||||
|
Загрузка…
x
Ссылка в новой задаче
Block a user