gecko-dev/network/cnvts/cvextcon.c

421 lines
12 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
#ifdef NTO
#include <sys/select.h> /* For definition of fd_set */
#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;
}