mirror of
https://github.com/MidnightCommander/mc
synced 2024-12-23 21:06:52 +03:00
589 lines
14 KiB
C
589 lines
14 KiB
C
/* {{{ Copyright */
|
|
|
|
/* Background support.
|
|
Copyright (C) 1996 The Free Software Foundation
|
|
|
|
Written by: 1996 Miguel de Icaza
|
|
|
|
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.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
|
|
|
|
/* }}} */
|
|
|
|
#include <config.h>
|
|
#include <stdarg.h>
|
|
#include <sys/types.h>
|
|
#ifdef HAVE_SYS_WAIT_H
|
|
# include <sys/wait.h>
|
|
#endif
|
|
#include <errno.h>
|
|
#ifdef HAVE_UNISTD_H
|
|
# include <unistd.h>
|
|
#endif
|
|
#include <sys/stat.h>
|
|
#include <sys/param.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include "tty.h"
|
|
#include "global.h"
|
|
#include "dlg.h"
|
|
#include "widget.h"
|
|
#include "wtools.h"
|
|
|
|
/*
|
|
* We currenlty only support one way of comunicating the background
|
|
* and foreground process by using the socketpair system call
|
|
*/
|
|
#ifdef USE_NETCODE
|
|
# include <sys/socket.h>
|
|
#endif
|
|
#include "dialog.h"
|
|
#include "fileopctx.h"
|
|
#include "key.h" /* For add_select_channel(), delete_select_channel() */
|
|
#include "eregex.h"
|
|
#include "file.h"
|
|
#include "filegui.h"
|
|
|
|
/* If true, this is a background process */
|
|
int we_are_background = 0;
|
|
|
|
#ifdef WITH_BACKGROUND
|
|
/* If set background tasks wait to be attached */
|
|
int background_wait = 0;
|
|
|
|
#ifndef HAVE_SOCKETPAIR
|
|
int socketpair(int, int, int, int fd[2]);
|
|
#endif
|
|
|
|
/* File descriptor for talking to our parent */
|
|
static int parent_fd;
|
|
|
|
#define MAXCALLARGS 4 /* Number of arguments supported */
|
|
#define mymsg "Desde el hijo\n\r"
|
|
|
|
struct TaskList *task_list = NULL;
|
|
|
|
static int background_attention (int fd, void *closure);
|
|
|
|
static void
|
|
register_task_running (FileOpContext *ctx, pid_t pid, int fd, char *info)
|
|
{
|
|
TaskList *new;
|
|
|
|
new = g_new (TaskList, 1);
|
|
new->pid = pid;
|
|
new->info = info;
|
|
new->state = Task_Running;
|
|
new->next = task_list;
|
|
new->fd = fd;
|
|
task_list = new;
|
|
|
|
add_select_channel (fd, background_attention, ctx);
|
|
}
|
|
|
|
void
|
|
unregister_task_running (pid_t pid, int fd)
|
|
{
|
|
TaskList *p = task_list;
|
|
TaskList *prev = 0;
|
|
|
|
while (p){
|
|
if (p->pid == pid){
|
|
if (prev)
|
|
prev->next = p->next;
|
|
else
|
|
task_list = p->next;
|
|
g_free (p->info);
|
|
g_free (p);
|
|
break;
|
|
}
|
|
prev = p;
|
|
p = p->next;
|
|
}
|
|
delete_select_channel (fd);
|
|
}
|
|
|
|
/*
|
|
* Try to make the Midnight Commander a background job
|
|
*
|
|
* Returns:
|
|
* 1 for parent
|
|
* 0 for child
|
|
* -1 on failure
|
|
*/
|
|
int
|
|
do_background (FileOpContext *ctx, char *info)
|
|
{
|
|
int comm [2]; /* control connection stream */
|
|
pid_t pid;
|
|
|
|
if (socketpair (AF_UNIX, SOCK_STREAM, 0, comm) == -1)
|
|
return -1;
|
|
|
|
if ((pid = fork ()) == -1)
|
|
return -1;
|
|
|
|
if (pid == 0){
|
|
int nullfd;
|
|
|
|
parent_fd = comm [1];
|
|
we_are_background = 1;
|
|
|
|
/* Make stdin/stdout/stderr point somewhere */
|
|
close (0);
|
|
close (1);
|
|
close (2);
|
|
|
|
if ((nullfd = open ("/dev/null", O_RDONLY)) != -1){
|
|
while (dup2 (nullfd, 0) == -1 && errno == EINTR)
|
|
;
|
|
while (dup2 (nullfd, 1) == -1 && errno == EINTR)
|
|
;
|
|
while (dup2 (nullfd, 2) == -1 && errno == EINTR)
|
|
;
|
|
}
|
|
|
|
/* To make it obvious if it fails, there is a bug report on this */
|
|
write (2, mymsg, sizeof (mymsg));
|
|
write (1, mymsg, sizeof (mymsg));
|
|
|
|
/* Just for debugging the background back end */
|
|
if (background_wait){
|
|
volatile int i = 1;
|
|
|
|
while (i)
|
|
;
|
|
}
|
|
return 0;
|
|
} else {
|
|
close (comm [1]);
|
|
ctx->pid = pid;
|
|
register_task_running (ctx, pid, comm [0], info);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static char *
|
|
background_title (char *str)
|
|
{
|
|
char *result = g_strconcat (_("Background process:"), str, NULL);
|
|
|
|
return result;
|
|
}
|
|
|
|
/* {{{ Routines that do the real job */
|
|
static void
|
|
real_message_1s (enum OperationMode mode, int *flags, char *title, char *str1)
|
|
{
|
|
char *full_title;
|
|
|
|
if (mode == Background)
|
|
full_title = background_title (title);
|
|
else
|
|
full_title = title;
|
|
|
|
message (*flags, title, str1);
|
|
|
|
if (title != full_title)
|
|
g_free (full_title);
|
|
}
|
|
|
|
static void
|
|
real_message_2s (enum OperationMode mode, int *flags, char *title, char *str1, char *str2)
|
|
{
|
|
char *full_title;
|
|
|
|
if (mode == Background)
|
|
full_title = background_title (title);
|
|
else
|
|
full_title = title;
|
|
|
|
message (*flags, title, str1, str2);
|
|
|
|
if (title != full_title)
|
|
g_free (full_title);
|
|
}
|
|
|
|
static void
|
|
real_message_3s (enum OperationMode mode, int *flags, char *title, char *str1, char *str2, const char *str3)
|
|
{
|
|
char *full_title;
|
|
|
|
if (mode == Background)
|
|
full_title = background_title (title);
|
|
else
|
|
full_title = title;
|
|
|
|
message (*flags, title, str1, str2, str3);
|
|
|
|
if (title != full_title)
|
|
g_free (full_title);
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ Parent handlers */
|
|
|
|
/* Parent/child protocol
|
|
*
|
|
* the child (the background) process send the following:
|
|
* void *routine -- routine to be invoked in the parent
|
|
* int nargc -- number of arguments
|
|
* int type -- Return argument type.
|
|
*
|
|
* If the routine is zero, then it is a way to tell the parent
|
|
* that the process is dying.
|
|
*
|
|
* nargc arguments in the following format:
|
|
* int size of the coming block
|
|
* size bytes with the block
|
|
*
|
|
* Now, the parent loads all those and then invokes
|
|
* the routine with pointers to the information passed
|
|
* (we just support pointers).
|
|
*
|
|
* If the return type is integer:
|
|
*
|
|
* the parent then writes an int to the child with
|
|
* the return value from the routine and the values
|
|
* of any global variable that is modified in the parent
|
|
* currently: do_append and recursive_result.
|
|
*
|
|
* If the return type is a string:
|
|
*
|
|
* the parent writes the resulting string lenght
|
|
* if the result string was NULL or the empty string,
|
|
* then the lenght is zero.
|
|
* The parent then writes the string lenght and frees
|
|
* the result string.
|
|
*/
|
|
/*
|
|
* Receive requests from background process and invoke the
|
|
* specified routine
|
|
*/
|
|
|
|
static int
|
|
background_attention (int fd, void *closure)
|
|
{
|
|
FileOpContext *ctx;
|
|
int have_ctx;
|
|
void *routine;
|
|
int argc, i, result, status;
|
|
char *data [MAXCALLARGS];
|
|
char *resstr;
|
|
int bytes;
|
|
enum ReturnType type;
|
|
char *background_process_error = _(" Background process error ");
|
|
|
|
ctx = closure;
|
|
|
|
bytes = read (fd, &routine, sizeof (routine));
|
|
if (bytes < (sizeof (routine))){
|
|
if (errno == ECHILD)
|
|
message (1, background_process_error, _(" Child died unexpectedly "));
|
|
else
|
|
message (1, background_process_error, _(" Unknown error in child "));
|
|
unregister_task_running (ctx->pid, fd);
|
|
waitpid (ctx->pid, &status, 0);
|
|
return 0;
|
|
}
|
|
|
|
/* If the routine is zero, then the child is telling us that he is dying */
|
|
if ((int) routine == MSG_CHILD_EXITING){
|
|
unregister_task_running (ctx->pid, fd);
|
|
waitpid (ctx->pid, &status, 0);
|
|
return 0;
|
|
}
|
|
|
|
read (fd, &argc, sizeof (argc));
|
|
if (argc > MAXCALLARGS){
|
|
message (1, _(" Background protocol error "),
|
|
_(" Background process sent us a request for more arguments \n"
|
|
" than we can handle. \n"));
|
|
}
|
|
read (fd, &type, sizeof (type));
|
|
read (fd, &have_ctx, sizeof (have_ctx));
|
|
if (have_ctx)
|
|
read (fd, ctx, sizeof (FileOpContext));
|
|
|
|
for (i = 0; i < argc; i++){
|
|
int size;
|
|
|
|
read (fd, &size, sizeof (size));
|
|
data [i] = g_malloc (size+1);
|
|
read (fd, data [i], size);
|
|
|
|
data [i][size] = 0; /* NULL terminate the blocks (they could be strings) */
|
|
}
|
|
|
|
/* Handle the call */
|
|
if (type == Return_Integer){
|
|
if (have_ctx)
|
|
switch (argc){
|
|
case 1:
|
|
result = (*(int (*)(int, char *))routine)(Background, data [0]);
|
|
break;
|
|
case 2:
|
|
result = (*(int (*)(int, char *, char *))routine)
|
|
(Background, data [0], data [1]);
|
|
break;
|
|
case 3:
|
|
result = (*(int (*)(int, char *, char *, char *))routine)
|
|
(Background, data [0], data [1], data [2]);
|
|
break;
|
|
case 4:
|
|
result = (*(int (*)(int, char *, char *, char *, char *))routine)
|
|
(Background, data [0], data [1], data [2], data [3]);
|
|
break;
|
|
}
|
|
else
|
|
switch (argc){
|
|
case 1:
|
|
result = (*(int (*)(FileOpContext *, int, char *))routine)
|
|
(ctx, Background, data [0]);
|
|
break;
|
|
case 2:
|
|
result = (*(int (*)(FileOpContext *, int, char *, char *))routine)
|
|
(ctx, Background, data [0], data [1]);
|
|
break;
|
|
case 3:
|
|
result = (*(int (*)(FileOpContext *, int, char *, char *, char *))routine)
|
|
(ctx, Background, data [0], data [1], data [2]);
|
|
break;
|
|
case 4:
|
|
result = (*(int (*)(FileOpContext *, int, char *, char *, char *, char *))routine)
|
|
(ctx, Background, data [0], data [1], data [2], data [3]);
|
|
break;
|
|
}
|
|
|
|
/* Send the result code and the value for shared variables */
|
|
write (fd, &result, sizeof (int));
|
|
if (have_ctx)
|
|
write (fd, ctx, sizeof (FileOpContext));
|
|
} else if (type == Return_String) {
|
|
int len;
|
|
|
|
/* FIXME: string routines should also use the Foreground/Background
|
|
* parameter. Currently, this is not used here
|
|
*/
|
|
switch (argc){
|
|
case 1:
|
|
resstr = (*(char * (*)(char *))routine)(data [0]);
|
|
break;
|
|
case 2:
|
|
resstr = (*(char * (*)(char *, char *))routine)
|
|
(data [0], data [1]);
|
|
break;
|
|
case 3:
|
|
resstr = (*(char * (*)(char *, char *, char *))routine)
|
|
(data [0], data [1], data [2]);
|
|
break;
|
|
case 4:
|
|
resstr = (*(char * (*)(char *, char *, char *, char *))routine)
|
|
(data [0], data [1], data [2], data [3]);
|
|
break;
|
|
}
|
|
if (resstr){
|
|
len = strlen (resstr);
|
|
write (fd, &len, sizeof (len));
|
|
if (len){
|
|
write (fd, resstr, len);
|
|
g_free (resstr);
|
|
}
|
|
} else {
|
|
len = 0;
|
|
write (fd, &len, sizeof (len));
|
|
}
|
|
}
|
|
for (i = 0; i < argc; i++)
|
|
g_free (data [i]);
|
|
|
|
do_refresh ();
|
|
mc_refresh ();
|
|
#ifndef HAVE_X
|
|
doupdate ();
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* }}} */
|
|
|
|
/* {{{ client RPC routines */
|
|
|
|
/* Sends the header for a call to a routine in the parent process. If the file
|
|
* operation context is not NULL, then it requests that the first parameter of
|
|
* the call be a file operation context.
|
|
*/
|
|
static void
|
|
parent_call_header (void *routine, int argc, enum ReturnType type, FileOpContext *ctx)
|
|
{
|
|
int have_ctx;
|
|
|
|
have_ctx = (ctx != NULL);
|
|
|
|
write (parent_fd, &routine, sizeof (routine));
|
|
write (parent_fd, &argc, sizeof (int));
|
|
write (parent_fd, &type, sizeof (type));
|
|
write (parent_fd, &have_ctx, sizeof (have_ctx));
|
|
|
|
if (have_ctx)
|
|
write (parent_fd, ctx, sizeof (FileOpContext));
|
|
}
|
|
|
|
int
|
|
parent_call (void *routine, FileOpContext *ctx, int argc, ...)
|
|
{
|
|
va_list ap;
|
|
int i;
|
|
|
|
va_start (ap, argc);
|
|
parent_call_header (routine, argc, Return_Integer, ctx);
|
|
for (i = 0; i < argc; i++) {
|
|
int len;
|
|
void *value;
|
|
|
|
len = va_arg (ap, int);
|
|
value = va_arg (ap, void *);
|
|
write (parent_fd, &len, sizeof (int));
|
|
write (parent_fd, value, len);
|
|
}
|
|
read (parent_fd, &i, sizeof (int));
|
|
if (ctx)
|
|
read (parent_fd, ctx, sizeof (FileOpContext));
|
|
|
|
return i;
|
|
}
|
|
|
|
static char *
|
|
parent_call_string (void *routine, int argc, ...)
|
|
{
|
|
va_list ap;
|
|
char *str;
|
|
int i;
|
|
|
|
va_start (ap, argc);
|
|
parent_call_header (routine, argc, Return_String, NULL);
|
|
for (i = 0; i < argc; i++){
|
|
int len;
|
|
void *value;
|
|
|
|
len = va_arg (ap, int);
|
|
value = va_arg (ap, void *);
|
|
write (parent_fd, &len, sizeof (int));
|
|
write (parent_fd, value, len);
|
|
}
|
|
read (parent_fd, &i, sizeof (int));
|
|
if (!i)
|
|
return NULL;
|
|
str = g_malloc (i + 1);
|
|
read (parent_fd, str, i);
|
|
str [i] = 0;
|
|
return str;
|
|
}
|
|
|
|
void
|
|
tell_parent (int msg)
|
|
{
|
|
write (parent_fd, &msg, sizeof (int));
|
|
}
|
|
|
|
void
|
|
message_1s (int flags, char *title, char *str1)
|
|
{
|
|
if (we_are_background)
|
|
parent_call ((void *)real_message_1s, NULL, 3, sizeof (flags), &flags,
|
|
strlen (title), title, strlen (str1), str1);
|
|
else
|
|
real_message_1s (Foreground, &flags, title, str1);
|
|
}
|
|
|
|
void
|
|
message_2s (int flags, char *title, char *str1, char *str2)
|
|
{
|
|
if (we_are_background)
|
|
parent_call ((void *)real_message_2s, NULL, 4, sizeof (flags), &flags,
|
|
strlen (title), title, strlen (str1), str1,
|
|
strlen (str2), str2);
|
|
else
|
|
real_message_2s (Foreground, &flags, title, str1, str2);
|
|
}
|
|
|
|
void
|
|
message_3s (int flags, char *title, char *str1, char *str2, const char *str3)
|
|
{
|
|
if (we_are_background)
|
|
parent_call ((void *)real_message_3s, NULL, 3, sizeof (flags), &flags,
|
|
strlen (title), title, strlen (str1), str1,
|
|
strlen (str2), str2, strlen (str3), str3);
|
|
else
|
|
real_message_3s (Foreground, &flags, title, str1, str2, str3);
|
|
}
|
|
|
|
char *
|
|
input_dialog_help (char *header, char *text, char *help, char *def_text)
|
|
{
|
|
extern char *real_input_dialog_help (char *header, char *text, char *help, char *def_text);
|
|
if (we_are_background)
|
|
return parent_call_string ((void *)real_input_dialog_help, 4,
|
|
strlen (header), header,
|
|
strlen (text), text,
|
|
strlen (help), help,
|
|
strlen (def_text), def_text);
|
|
else
|
|
return real_input_dialog_help (header, text, help, def_text);
|
|
}
|
|
|
|
#else /* Else => No background code support */
|
|
|
|
/* {{{ Stubs if background code is not supported */
|
|
void
|
|
message_1s (int flags, char *title, char *str1)
|
|
{
|
|
message (flags, title, str1);
|
|
}
|
|
|
|
void
|
|
message_2s (int flags, char *title, char *str1, char *str2)
|
|
{
|
|
message (flags, title, str1, str2);
|
|
}
|
|
|
|
void
|
|
message_3s (int flags, char *title, char *str1, char *str2, const char *str3)
|
|
{
|
|
message (flags, title, str1, str2, str3);
|
|
}
|
|
|
|
char *
|
|
input_dialog_help (char *header, char *text, char *help, char *def_text)
|
|
{
|
|
return real_input_dialog_help (header, text, help, def_text);
|
|
}
|
|
/* }}} */
|
|
|
|
#endif
|
|
|
|
/* {{{ Functions shared between background and foreground */
|
|
|
|
void
|
|
message_1s1d (int flags, char *title, char *str, int d)
|
|
{
|
|
char *p = g_strdup_printf (str, d);
|
|
message_1s (flags, title, p);
|
|
g_free (p);
|
|
}
|
|
|
|
/* }}} */
|