зеркало из https://github.com/mozilla/pjs.git
417 строки
11 KiB
C
417 строки
11 KiB
C
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
*
|
|
* The contents of this file are subject to the Netscape Public License
|
|
* Version 1.0 (the "NPL"); you may not use this file except in
|
|
* compliance with the NPL. You may obtain a copy of the NPL at
|
|
* http://www.mozilla.org/NPL/
|
|
*
|
|
* Software distributed under the NPL is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
|
|
* for the specific language governing rights and limitations under the
|
|
* NPL.
|
|
*
|
|
* The Initial Developer of this code under the NPL is Netscape
|
|
* Communications Corporation. Portions created by Netscape are
|
|
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
|
|
* Reserved.
|
|
*/
|
|
/* cvextcon.c --- using external Unix programs as content-encoding filters.
|
|
*/
|
|
|
|
#include "xp.h"
|
|
#include "prmem.h"
|
|
#include "plstr.h"
|
|
#include "netutils.h"
|
|
#include "mkselect.h"
|
|
#include "mktcp.h"
|
|
#include "mkgeturl.h"
|
|
#include "cvextcon.h"
|
|
#include "mkformat.h"
|
|
|
|
#include <fcntl.h>
|
|
#include <sys/wait.h>
|
|
#include <signal.h>
|
|
|
|
#ifdef __sgi
|
|
#include <bstring.h> /* FD_ZERO uses bzero() which needs this */
|
|
/* file for its prototype. */
|
|
#endif
|
|
|
|
typedef struct _CVG_DataObject {
|
|
NET_StreamClass *next_stream; /* Where the output goes */
|
|
pid_t pid; /* process in which the filter is running */
|
|
int infd; /* for reading from the process */
|
|
int outfd; /* for writing to the process */
|
|
struct sigaction oldact; /* Old SIGCHLD handler */
|
|
} CVG_DataObject;
|
|
|
|
|
|
PRIVATE int net_ExtConverterRead (CVG_DataObject *data, PRBool block_p)
|
|
{
|
|
char input_buffer [1024];
|
|
int bytes_read;
|
|
|
|
AGAIN:
|
|
while ((bytes_read = read (data->infd, input_buffer, sizeof (input_buffer)))
|
|
> 0)
|
|
{
|
|
if (data->next_stream)
|
|
{
|
|
int status = ((*data->next_stream->put_block)
|
|
(data->next_stream,
|
|
input_buffer, bytes_read));
|
|
/* abort */
|
|
if (status < 0)
|
|
return status;
|
|
}
|
|
}
|
|
|
|
/* It's necessary that we block here waiting for the process to produce
|
|
the rest of its output before we allow the `complete' method to return.
|
|
We've already set the socket to be nonblocking, and there doesn't appear
|
|
to be any way to set it to do blocking reads again, so instead, we'll
|
|
use select() to block for it. That will return when there is some input
|
|
available, and we'll read it, and (maybe) block again, repeating until
|
|
we get an EOF.
|
|
|
|
To implement this in a non-blocking way would require the input and
|
|
output sides of this to be disconnected - the output side would be as in
|
|
this file, but the input side would need to be a new stream type in
|
|
NET_ProcessNet(), at the level of http, ftp, and file streams.
|
|
*/
|
|
if (bytes_read == -1 && block_p &&
|
|
(errno == EAGAIN || errno == EWOULDBLOCK))
|
|
{
|
|
fd_set rset;
|
|
FD_ZERO (&rset);
|
|
FD_SET (data->infd, &rset);
|
|
if (select (data->infd+1, &rset, 0, 0, 0) < 0)
|
|
perror ("select");
|
|
goto AGAIN;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
PRIVATE int net_ExtConverterWrite (NET_StreamClass *stream,
|
|
CONST char* output_buffer,
|
|
int32 output_length)
|
|
{
|
|
CVG_DataObject *data = (CVG_DataObject *) stream->data_object;
|
|
|
|
while (output_length > 0)
|
|
{
|
|
int bytes_written = 0;
|
|
|
|
/* write as much as possible (until done, or the pipe is full.)
|
|
*/
|
|
while (output_length > 0 &&
|
|
(bytes_written = write (data->outfd, output_buffer,
|
|
output_length))
|
|
> 0)
|
|
{
|
|
output_buffer += bytes_written;
|
|
output_length -= bytes_written;
|
|
}
|
|
|
|
if (bytes_written == -1 && errno != EAGAIN && errno != EWOULDBLOCK)
|
|
{
|
|
perror ("write");
|
|
return -1;
|
|
}
|
|
|
|
/* Now read as much as possible (until done, or the pipe is drained.)
|
|
*/
|
|
{
|
|
int status = net_ExtConverterRead (data, PR_FALSE);
|
|
/* abort */
|
|
if (status < 0)
|
|
return status;
|
|
}
|
|
|
|
/* Now go around the loop again, if we weren't able to write all of
|
|
the output buffer at once (because the pipe filled up.) Now that
|
|
we've read the available data from the pipe, we will presumably
|
|
be able to write to it again.
|
|
*/
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
PRIVATE int net_ExtConverterWriteReady (NET_StreamClass *stream)
|
|
{
|
|
/* #### I'm not sure what the right thing to do here is. --jwz */
|
|
#if 1
|
|
return (MAX_WRITE_READY);
|
|
#else
|
|
CVG_DataObject *data = (CVG_DataObject *) stream->data_object;
|
|
if(data->next_stream)
|
|
return ((*data->next_stream->is_write_ready)
|
|
(data->next_stream));
|
|
else
|
|
return (MAX_WRITE_READY);
|
|
#endif
|
|
}
|
|
|
|
|
|
PRIVATE void
|
|
net_KillConverterProcess (CVG_DataObject *data)
|
|
{
|
|
pid_t wait_status;
|
|
/* It may not actually be necessary to kill the process here if we have
|
|
exited normally, since in that case it has already closed its stdout;
|
|
but it can't hurt.
|
|
|
|
After it dies, we have to wait() for it, or it becomes a zombie.
|
|
I'm not entirely sure that the perror() is correct - it could be that
|
|
0 is a legitimate value from waitpid() if the process had already
|
|
exited before we kill()ed it, but I'm not sure.
|
|
*/
|
|
|
|
kill (data->pid, SIGINT);
|
|
wait_status = waitpid (data->pid, 0, 0);
|
|
#ifdef DEBUG_dp
|
|
fprintf(stderr, "Restoring sigchild handler for pid %d.\n", data->pid);
|
|
#endif
|
|
/* Reset SIGCHLD signal hander before returning */
|
|
sigaction(SIGCHLD, &data->oldact, NULL);
|
|
|
|
if (wait_status != data->pid)
|
|
perror ("waitpid");
|
|
}
|
|
|
|
PRIVATE void net_ExtConverterComplete (NET_StreamClass *stream)
|
|
{
|
|
CVG_DataObject *data = (CVG_DataObject *) stream->data_object;
|
|
|
|
/* Send an EOF to the stdin of the subprocess; then wait for the rest
|
|
of its output to show up on its stdout; then close stdout, and kill
|
|
the process. */
|
|
close (data->outfd);
|
|
net_ExtConverterRead (data, PR_TRUE);
|
|
close (data->infd);
|
|
net_KillConverterProcess (data);
|
|
|
|
/* complete the next stream */
|
|
if (data->next_stream)
|
|
{
|
|
(*data->next_stream->complete) (data->next_stream);
|
|
free (data->next_stream);
|
|
}
|
|
free (data);
|
|
}
|
|
|
|
PRIVATE void net_ExtConverterAbort (NET_StreamClass *stream, int status)
|
|
{
|
|
CVG_DataObject *data = (CVG_DataObject *) stream->data_object;
|
|
|
|
/* Close the streams and kill the process, discarding any output still
|
|
in the pipe. */
|
|
close (data->outfd);
|
|
close (data->infd);
|
|
net_KillConverterProcess (data);
|
|
|
|
/* abort the next stream */
|
|
if (data->next_stream)
|
|
{
|
|
(*data->next_stream->abort) (data->next_stream, status);
|
|
free (data->next_stream);
|
|
}
|
|
free (data);
|
|
}
|
|
|
|
|
|
PUBLIC NET_StreamClass *
|
|
NET_ExtConverterConverter (int format_out,
|
|
void *data_obj,
|
|
URL_Struct *URL_s,
|
|
MWContext *window_id)
|
|
{
|
|
CVG_DataObject* obj;
|
|
CV_ExtConverterStruct * ext_con_obj = (CV_ExtConverterStruct *) data_obj;
|
|
NET_StreamClass* stream;
|
|
struct sigaction newact;
|
|
|
|
TRACEMSG(("Setting up display stream. Have URL: %s\n", URL_s->address));
|
|
|
|
stream = PR_NEW(NET_StreamClass);
|
|
if(stream == NULL)
|
|
return(NULL);
|
|
|
|
memset(stream, 0, sizeof(NET_StreamClass));
|
|
|
|
obj = PR_NEW(CVG_DataObject);
|
|
if (obj == NULL)
|
|
return(NULL);
|
|
memset(obj, 0, sizeof(CVG_DataObject));
|
|
|
|
stream->name = "Filter Stream";
|
|
stream->complete = (MKStreamCompleteFunc) net_ExtConverterComplete;
|
|
stream->abort = (MKStreamAbortFunc) net_ExtConverterAbort;
|
|
stream->put_block = (MKStreamWriteFunc) net_ExtConverterWrite;
|
|
stream->is_write_ready = (MKStreamWriteReadyFunc) net_ExtConverterWriteReady;
|
|
stream->data_object = obj; /* document info object */
|
|
stream->window_id = window_id;
|
|
|
|
/* Open the next stream.
|
|
First, swap in the content-encoding or content-type of the document
|
|
as we are about to convert it, to look up the proper next converter
|
|
(and to avoid looping.) But once we have set up the stream, put the
|
|
original encoding back, so that the URL_Struct is not permanently
|
|
altered - foo.html.gz must still have content-encoding x-gzip even
|
|
though it has been decoded for display.
|
|
*/
|
|
{
|
|
char *old, *new;
|
|
if (ext_con_obj->is_encoding_converter)
|
|
{
|
|
old = URL_s->content_encoding;
|
|
new = PL_strdup (ext_con_obj->new_format);
|
|
if (!new) return (NULL);
|
|
URL_s->content_encoding = new;
|
|
}
|
|
else
|
|
{
|
|
old = URL_s->content_type;
|
|
new = PL_strdup (ext_con_obj->new_format);
|
|
if (!new) return (NULL);
|
|
URL_s->content_type = new;
|
|
}
|
|
|
|
obj->next_stream = NET_StreamBuilder (format_out, URL_s, window_id);
|
|
|
|
if (ext_con_obj->is_encoding_converter)
|
|
{
|
|
PR_Free (URL_s->content_encoding);
|
|
URL_s->content_encoding = old;
|
|
}
|
|
else
|
|
{
|
|
PR_Free (URL_s->content_type);
|
|
URL_s->content_type = old;
|
|
}
|
|
}
|
|
|
|
if (!obj->next_stream)
|
|
return (NULL);
|
|
|
|
/* Open two pipes, one for writing to a subprocess, and one for reading
|
|
from it (for a total of four file descriptors, I/O for us, and O/I
|
|
for the kid.)
|
|
*/
|
|
{
|
|
int infds [2];
|
|
int outfds[2];
|
|
pid_t forked;
|
|
|
|
if (pipe (infds))
|
|
{
|
|
perror ("creating input pipe");
|
|
free (stream);
|
|
free (obj);
|
|
return 0;
|
|
}
|
|
if (pipe (outfds))
|
|
{
|
|
perror ("creating output pipe");
|
|
free (stream);
|
|
free (obj);
|
|
return 0;
|
|
}
|
|
obj->infd = infds [0];
|
|
obj->outfd = outfds [1];
|
|
|
|
/* Set our side of the pipes to be nonblocking. (It's important not
|
|
to set the other side of the pipes to be nonblocking - that
|
|
decision must be left up to the process on the other end. */
|
|
|
|
#if defined(O_NONBLOCK)
|
|
# define NONBLOCK_FLAG O_NONBLOCK
|
|
#elif defined(O_NDELAY)
|
|
# define NONBLOCK_FLAG O_NDELAY
|
|
#else
|
|
ERROR!! neither O_NONBLOCK nor O_NDELAY are defined.
|
|
#endif
|
|
fcntl (obj->infd, F_SETFL, NONBLOCK_FLAG);
|
|
fcntl (obj->outfd, F_SETFL, NONBLOCK_FLAG);
|
|
#undef NONBLOCK_FLAG
|
|
|
|
obj->pid = 0;
|
|
|
|
#ifdef DEBUG_dp
|
|
fprintf(stderr, "Ignoring sigchild.\n");
|
|
#endif
|
|
/* Setup signals so that SIGCHLD is ignored as we want to do waitpid
|
|
* when the helperapp ends
|
|
*/
|
|
newact.sa_handler = SIG_DFL;
|
|
newact.sa_flags = 0;
|
|
sigfillset(&newact.sa_mask);
|
|
sigaction (SIGCHLD, &newact, &obj->oldact);
|
|
|
|
switch (forked = fork ())
|
|
{
|
|
case -1:
|
|
perror ("fork");
|
|
close (outfds[0]);
|
|
close (outfds[1]);
|
|
close (infds [0]);
|
|
close (infds [1]);
|
|
free (stream);
|
|
free (obj);
|
|
/* Reset SIGCHLD signal hander before returning */
|
|
sigaction(SIGCHLD, &obj->oldact, NULL);
|
|
return 0;
|
|
case 0:
|
|
{
|
|
/* This is the new process. exec() the filter here.
|
|
We do this with sh to get tokenization and pipelines
|
|
and all that junk.
|
|
*/
|
|
char *av[10];
|
|
int ac = 0;
|
|
|
|
av [ac++] = "/bin/sh";
|
|
av [ac++] = "-c";
|
|
av [ac++] = ext_con_obj->command;
|
|
av [ac++] = 0;
|
|
|
|
dup2 (outfds[0], 0); /* stdin */
|
|
dup2 (infds [1], 1); /* stdout */
|
|
/* dup2 (infds [1], 2); * stderr */
|
|
|
|
/* We have copied the two pipes to stdin/stdout.
|
|
We no longer need the other file descriptors hanging around.
|
|
(Actually I think we need to close these, or the other side
|
|
of the pipe doesn't see an eof when we close stdout...)
|
|
*/
|
|
close (outfds[0]);
|
|
close (outfds[1]);
|
|
close (infds [0]);
|
|
close (infds [1]);
|
|
|
|
execv (av[0], av);
|
|
/* exec() should never return. */
|
|
perror ("execv");
|
|
exit (1); /* This only exits a child fork. */
|
|
break;
|
|
}
|
|
default:
|
|
/* This is the "old" process (subproc pid is in `forked'.) */
|
|
obj->pid = forked;
|
|
|
|
/* These are the file descriptors we created for the benefit
|
|
of the child process - we don't need them in the parent. */
|
|
close (outfds[0]);
|
|
close (infds [1]);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
TRACEMSG(("Returning stream from NET_ExtConverterConverter\n"));
|
|
|
|
return stream;
|
|
}
|