home *** CD-ROM | disk | FTP | other *** search
/ Tools / WinSN5.0Ver.iso / NETSCAP.50 / WIN1998.ZIP / ns / lib / libnet / cvextcon.c < prev    next >
Encoding:
C/C++ Source or Header  |  1998-04-08  |  11.7 KB  |  413 lines

  1. /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  2.  *
  3.  * The contents of this file are subject to the Netscape Public License
  4.  * Version 1.0 (the "NPL"); you may not use this file except in
  5.  * compliance with the NPL.  You may obtain a copy of the NPL at
  6.  * http://www.mozilla.org/NPL/
  7.  *
  8.  * Software distributed under the NPL is distributed on an "AS IS" basis,
  9.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
  10.  * for the specific language governing rights and limitations under the
  11.  * NPL.
  12.  *
  13.  * The Initial Developer of this code under the NPL is Netscape
  14.  * Communications Corporation.  Portions created by Netscape are
  15.  * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
  16.  * Reserved.
  17.  */
  18. /* cvextcon.c --- using external Unix programs as content-encoding filters.
  19.  */
  20.  
  21. #include "mkutils.h"
  22. #include "mkstream.h"
  23. #include "mkgeturl.h"
  24. #include "xp.h"
  25. #include "cvextcon.h"
  26. #include "mkformat.h"
  27.  
  28. #include <fcntl.h>
  29. #include <sys/wait.h>
  30.  
  31. #ifdef __sgi
  32. #include <bstring.h>            /* FD_ZERO uses bzero() which needs this */
  33.                                 /* file for its prototype. */
  34. #endif
  35.  
  36. typedef struct _CVG_DataObject {
  37.   NET_StreamClass *next_stream;    /* Where the output goes */
  38.   pid_t pid;                    /* process in which the filter is running */
  39.   int infd;                        /* for reading from the process */
  40.   int outfd;                    /* for writing to the process */
  41.   struct sigaction oldact;        /* Old SIGCHLD handler */
  42. } CVG_DataObject;
  43.  
  44.  
  45. PRIVATE int net_ExtConverterRead (CVG_DataObject *data, Bool block_p)
  46. {
  47.   char input_buffer [1024];
  48.   int bytes_read;
  49.  
  50.  AGAIN:
  51.   while ((bytes_read = read (data->infd, input_buffer, sizeof (input_buffer)))
  52.          > 0)
  53.     {
  54.       if (data->next_stream)
  55.         {
  56.           int status = ((*data->next_stream->put_block)
  57.                         (data->next_stream,
  58.                          input_buffer, bytes_read));
  59.           /* abort */
  60.           if (status < 0)
  61.             return status;
  62.         }
  63.     }
  64.  
  65.   /* It's necessary that we block here waiting for the process to produce
  66.      the rest of its output before we allow the `complete' method to return.
  67.      We've already set the socket to be nonblocking, and there doesn't appear
  68.      to be any way to set it to do blocking reads again, so instead, we'll
  69.      use select() to block for it.  That will return when there is some input
  70.      available, and we'll read it, and (maybe) block again, repeating until
  71.      we get an EOF.
  72.  
  73.      To implement this in a non-blocking way would require the input and
  74.      output sides of this to be disconnected - the output side would be as in
  75.      this file, but the input side would need to be a new stream type in
  76.      NET_ProcessNet(), at the level of http, ftp, and file streams.  
  77.    */
  78.   if (bytes_read == -1 && block_p &&
  79.       (errno == EAGAIN || errno == EWOULDBLOCK))
  80.     {
  81.       fd_set rset;
  82.       FD_ZERO (&rset);
  83.       FD_SET (data->infd, &rset);
  84.       if (select (data->infd+1, &rset, 0, 0, 0) < 0)
  85.         perror ("select");
  86.       goto AGAIN;
  87.     }
  88.  
  89.   return 1;
  90. }
  91.  
  92.  
  93. PRIVATE int net_ExtConverterWrite (NET_StreamClass *stream,
  94.                                    CONST char* output_buffer,
  95.                                    int32 output_length)
  96. {
  97.   CVG_DataObject *data = (CVG_DataObject *) stream->data_object;  
  98.  
  99.   while (output_length > 0)
  100.     {
  101.       int bytes_written = 0;
  102.  
  103.       /* write as much as possible (until done, or the pipe is full.)
  104.        */
  105.       while (output_length > 0 &&
  106.              (bytes_written = write (data->outfd, output_buffer,
  107.                                      output_length))
  108.              > 0)
  109.         {
  110.           output_buffer += bytes_written;
  111.           output_length -= bytes_written;
  112.         }
  113.  
  114.       if (bytes_written == -1 && errno != EAGAIN && errno != EWOULDBLOCK)
  115.         {
  116.           perror ("write");
  117.           return -1;
  118.         }
  119.  
  120.       /* Now read as much as possible (until done, or the pipe is drained.)
  121.        */
  122.       {
  123.         int status = net_ExtConverterRead (data, FALSE);
  124.         /* abort */
  125.         if (status < 0)
  126.           return status;
  127.       }
  128.  
  129.       /* Now go around the loop again, if we weren't able to write all of
  130.          the output buffer at once (because the pipe filled up.)  Now that
  131.          we've read the available data from the pipe, we will presumably
  132.          be able to write to it again.
  133.        */
  134.     }
  135.   return 1;
  136. }
  137.  
  138. PRIVATE int net_ExtConverterWriteReady (NET_StreamClass *stream)
  139. {    
  140.   /* #### I'm not sure what the right thing to do here is. --jwz */
  141. #if 1
  142.   return (MAX_WRITE_READY);
  143. #else
  144.   CVG_DataObject *data = (CVG_DataObject *) stream->data_object;
  145.   if(data->next_stream)
  146.     return ((*data->next_stream->is_write_ready)
  147.             (data->next_stream));
  148.   else
  149.     return (MAX_WRITE_READY);
  150. #endif
  151. }
  152.  
  153.  
  154. PRIVATE void
  155. net_KillConverterProcess (CVG_DataObject *data)
  156. {
  157.   pid_t wait_status;
  158.   /* It may not actually be necessary to kill the process here if we have
  159.      exited normally, since in that case it has already closed its stdout;
  160.      but it can't hurt.
  161.  
  162.      After it dies, we have to wait() for it, or it becomes a zombie.
  163.      I'm not entirely sure that the perror() is correct - it could be that
  164.      0 is a legitimate value from waitpid() if the process had already
  165.      exited before we kill()ed it, but I'm not sure.
  166.    */
  167.  
  168.   kill (data->pid, SIGINT);
  169.   wait_status = waitpid (data->pid, 0, 0);
  170. #ifdef DEBUG_dp
  171.   fprintf(stderr, "Restoring sigchild handler for pid %d.\n", data->pid);
  172. #endif
  173.   /* Reset SIGCHLD signal hander before returning */
  174.   sigaction(SIGCHLD, &data->oldact, NULL);
  175.  
  176.   if (wait_status != data->pid)
  177.     perror ("waitpid");
  178. }
  179.  
  180. PRIVATE void net_ExtConverterComplete (NET_StreamClass *stream)
  181. {
  182.   CVG_DataObject *data = (CVG_DataObject *) stream->data_object;  
  183.  
  184.   /* Send an EOF to the stdin of the subprocess; then wait for the rest
  185.      of its output to show up on its stdout; then close stdout, and kill
  186.      the process. */
  187.   close (data->outfd);
  188.   net_ExtConverterRead (data, TRUE);
  189.   close (data->infd);
  190.   net_KillConverterProcess (data);
  191.  
  192.   /* complete the next stream */
  193.   if (data->next_stream)
  194.     {
  195.       (*data->next_stream->complete) (data->next_stream);
  196.       free (data->next_stream);
  197.     }
  198.   free (data);
  199. }
  200.  
  201. PRIVATE void net_ExtConverterAbort (NET_StreamClass *stream, int status)
  202. {
  203.   CVG_DataObject *data = (CVG_DataObject *) stream->data_object;  
  204.  
  205.   /* Close the streams and kill the process, discarding any output still
  206.      in the pipe. */
  207.   close (data->outfd);
  208.   close (data->infd);
  209.   net_KillConverterProcess (data);
  210.  
  211.   /* abort the next stream */
  212.   if (data->next_stream)
  213.     {
  214.       (*data->next_stream->abort) (data->next_stream, status);
  215.       free (data->next_stream);
  216.     }
  217.   free (data);
  218. }
  219.  
  220.  
  221. PUBLIC NET_StreamClass * 
  222. NET_ExtConverterConverter (int          format_out,
  223.                            void        *data_obj,
  224.                            URL_Struct  *URL_s,
  225.                            MWContext   *window_id)
  226. {
  227.     CVG_DataObject* obj;
  228.     CV_ExtConverterStruct * ext_con_obj = (CV_ExtConverterStruct *) data_obj;
  229.     NET_StreamClass* stream;
  230.     struct sigaction newact;
  231.     
  232.     TRACEMSG(("Setting up display stream. Have URL: %s\n", URL_s->address));
  233.  
  234.     stream = XP_NEW(NET_StreamClass);
  235.     if(stream == NULL) 
  236.             return(NULL);
  237.  
  238.     XP_MEMSET(stream, 0, sizeof(NET_StreamClass));
  239.  
  240.     obj = XP_NEW(CVG_DataObject);
  241.     if (obj == NULL) 
  242.             return(NULL);
  243.     memset(obj, 0, sizeof(CVG_DataObject));
  244.  
  245.     stream->name           = "Filter Stream";
  246.     stream->complete       = (MKStreamCompleteFunc) net_ExtConverterComplete;
  247.     stream->abort          = (MKStreamAbortFunc) net_ExtConverterAbort;
  248.     stream->put_block      = (MKStreamWriteFunc) net_ExtConverterWrite;
  249.     stream->is_write_ready = (MKStreamWriteReadyFunc) net_ExtConverterWriteReady;
  250.     stream->data_object    = obj;  /* document info object */
  251.     stream->window_id      = window_id;
  252.  
  253.     /* Open the next stream.
  254.        First, swap in the content-encoding or content-type of the document
  255.        as we are about to convert it, to look up the proper next converter
  256.        (and to avoid looping.)  But once we have set up the stream, put the
  257.        original encoding back, so that the URL_Struct is not permanently
  258.        altered - foo.html.gz must still have content-encoding x-gzip even
  259.        though it has been decoded for display.
  260.      */
  261.     {
  262.       char *old, *new;
  263.       if (ext_con_obj->is_encoding_converter)
  264.         {
  265.           old = URL_s->content_encoding;
  266.           new = XP_STRDUP (ext_con_obj->new_format);
  267.           if (!new) return (NULL);
  268.           URL_s->content_encoding = new;
  269.         }
  270.       else
  271.         {
  272.           old = URL_s->content_type;
  273.           new = XP_STRDUP (ext_con_obj->new_format);
  274.           if (!new) return (NULL);
  275.           URL_s->content_type = new;
  276.         }
  277.  
  278.       obj->next_stream = NET_StreamBuilder (format_out, URL_s, window_id);
  279.  
  280.       if (ext_con_obj->is_encoding_converter)
  281.         {
  282.           XP_FREE (URL_s->content_encoding);
  283.           URL_s->content_encoding = old;
  284.         }
  285.       else
  286.         {
  287.           XP_FREE (URL_s->content_type);
  288.           URL_s->content_type = old;
  289.         }
  290.     }
  291.  
  292.     if (!obj->next_stream)
  293.       return (NULL);
  294.  
  295.     /* Open two pipes, one for writing to a subprocess, and one for reading
  296.        from it (for a total of four file descriptors, I/O for us, and O/I
  297.        for the kid.)
  298.      */
  299.     {
  300.       int infds [2];
  301.       int outfds[2];
  302.       pid_t forked;
  303.  
  304.       if (pipe (infds))
  305.         {
  306.           perror ("creating input pipe");
  307.           free (stream);
  308.           free (obj);
  309.           return 0;
  310.         }
  311.       if (pipe (outfds))
  312.         {
  313.           perror ("creating output pipe");
  314.           free (stream);
  315.           free (obj);
  316.           return 0;
  317.         }
  318.       obj->infd  = infds  [0];
  319.       obj->outfd = outfds [1];
  320.  
  321.       /* Set our side of the pipes to be nonblocking.  (It's important not
  322.          to set the other side of the pipes to be nonblocking - that
  323.          decision must be left up to the process on the other end. */
  324.  
  325. #if defined(O_NONBLOCK)
  326. # define NONBLOCK_FLAG O_NONBLOCK
  327. #elif defined(O_NDELAY)
  328. # define NONBLOCK_FLAG O_NDELAY
  329. #else
  330.       ERROR!! neither O_NONBLOCK nor O_NDELAY are defined.
  331. #endif
  332.       fcntl (obj->infd,  F_SETFL, NONBLOCK_FLAG);
  333.       fcntl (obj->outfd, F_SETFL, NONBLOCK_FLAG);
  334. #undef NONBLOCK_FLAG
  335.  
  336.       obj->pid = 0;
  337.  
  338. #ifdef DEBUG_dp
  339.   fprintf(stderr, "Ignoring sigchild.\n");
  340. #endif
  341.       /* Setup signals so that SIGCHLD is ignored as we want to do waitpid
  342.        * when the helperapp ends
  343.        */
  344.       newact.sa_handler = SIG_DFL;
  345.       newact.sa_flags = 0;
  346.       sigfillset(&newact.sa_mask);
  347.       sigaction (SIGCHLD, &newact, &obj->oldact);
  348.  
  349.       switch (forked = fork ())
  350.         {
  351.         case -1:
  352.           perror ("fork");
  353.           close (outfds[0]);
  354.           close (outfds[1]);
  355.           close (infds [0]);
  356.           close (infds [1]);
  357.           free (stream);
  358.           free (obj);
  359.           /* Reset SIGCHLD signal hander before returning */
  360.           sigaction(SIGCHLD, &obj->oldact, NULL);
  361.           return 0;
  362.         case 0:
  363.           {
  364.             /* This is the new process.  exec() the filter here.
  365.                We do this with sh to get tokenization and pipelines
  366.                and all that junk.
  367.              */
  368.             char *av[10];
  369.             int ac = 0;
  370.  
  371.             av [ac++] = "/bin/sh";
  372.             av [ac++] = "-c";
  373.             av [ac++] = ext_con_obj->command;
  374.             av [ac++] = 0;
  375.  
  376.             dup2 (outfds[0], 0);        /* stdin */
  377.             dup2 (infds [1], 1);        /* stdout */
  378.         /*    dup2 (infds [1], 2);         * stderr */
  379.  
  380.             /* We have copied the two pipes to stdin/stdout.
  381.                We no longer need the other file descriptors hanging around.
  382.                (Actually I think we need to close these, or the other side
  383.                of the pipe doesn't see an eof when we close stdout...)
  384.              */
  385.             close (outfds[0]);
  386.             close (outfds[1]);
  387.             close (infds [0]);
  388.             close (infds [1]);
  389.  
  390.             execv (av[0], av);
  391.             /* exec() should never return. */
  392.             perror ("execv");
  393.             exit (1);    /* This only exits a child fork.  */
  394.             break;
  395.           }
  396.         default:
  397.           /* This is the "old" process (subproc pid is in `forked'.) */
  398.           obj->pid = forked;
  399.  
  400.           /* These are the file descriptors we created for the benefit
  401.              of the child process - we don't need them in the parent. */
  402.           close (outfds[0]);
  403.           close (infds [1]);
  404.  
  405.           break;
  406.         }
  407.     }
  408.  
  409.     TRACEMSG(("Returning stream from NET_ExtConverterConverter\n"));
  410.  
  411.     return stream;
  412. }
  413.