home *** CD-ROM | disk | FTP | other *** search
- /* -*- 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 "mkutils.h"
- #include "mkstream.h"
- #include "mkgeturl.h"
- #include "xp.h"
- #include "cvextcon.h"
- #include "mkformat.h"
-
- #include <fcntl.h>
- #include <sys/wait.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, Bool 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, 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, 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 = XP_NEW(NET_StreamClass);
- if(stream == NULL)
- return(NULL);
-
- XP_MEMSET(stream, 0, sizeof(NET_StreamClass));
-
- obj = XP_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 = XP_STRDUP (ext_con_obj->new_format);
- if (!new) return (NULL);
- URL_s->content_encoding = new;
- }
- else
- {
- old = URL_s->content_type;
- new = XP_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)
- {
- XP_FREE (URL_s->content_encoding);
- URL_s->content_encoding = old;
- }
- else
- {
- XP_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;
- }
-