Frank Rehberger
Homepage

General IO set (gios)

thin and portable

Download: gios-0.1beta.tar.gz

This library presents a thin portable layer on top of operating systems API like Gnu/Linux and Windows-NT.  If using this library allmost no changes have to be done to port your  IO modules from Unix to Windows and versus. It allows shared files on windows and unified error code management.

One of the key problems in porting applications from Unix to Windows is "fork"ing. On Unix "fork"ing of subprocesses is common, using specific design patterns for resouce mamanagement. For Windows "fork"ing is not supported but inherit flags can be set per resource, so allmost the same resouce management can be realized as on Unix. If you dont mind changing old habbits of Unix programming, this is the right portable API for you.

Note: If using this API make sure your applications are calling  g_ios_init() before doing any IO. Otherwise your code is not portable to Windows-NT platform and child processes might abort  at once on Windows platform. This limitation (and also restricted signal management) will be solved in future.

API of most IO operations is the same as known from Unix-libc, extended by GError argument for unified error management on Unix and Windows. Only g_process operations differ from Unix.

Lets see, how to spawn subprocesses with gios library, doing  resource management propperly:

Function  creates new child and redirects stdin, stdout and stderr.
#include <glib-2.0/glib.h>

#define READ   0
#define WRITE  1

/* this function creates a child process using the argument list argv,
 * and returns process handler of newly created child and write-end of
 * pipe. The latter can be used to send child process data on stdin.
 *
 * @arg argv [in] program name and command line parameters
 * @arg to_child [out] stream to  stdin of child process.
 * @arg from_child [out] stream from stdout of child process.
 * @arg err [out] in case of errors, returns error code and message.
 *
 * @return 0 if failing, otherwise process handle of child, do not
 * assume process handle is equal to PID of child process. Only use
 * g_process operations for process synchronization.
 */
gproc
create_child (char* argv[], gint* in, gint *out, GError** err)
{
   gproc   child         = 0;
   gint    to_child[2]   = {-1, -1};
   gint    from_child[2] = {-1, -1};

   g_assert (*err==NULL);

   /* create pipe, both ends are inheritable */
   if (g_pipe (to_child, err)==-1)
      goto raise_error;
   if (g_pipe (from_child, err)==-1)
      goto raise_error:
  
   /* create non-inheritable ends of pipes, that shall be private to
    * parent process only */
   if ((*in=g_dup (to_child[WRITE], FALSE, err))==-1)
      goto raise_error;  
   if ((*out=g_dup (to_child[WRITE], FALSE, err))==-1)
      goto raise_error;
  
   /* close ends of pipe that shall not be inherited to child
      process */

   if (g_close (to_child[WRITE], err)==-1)
       goto raise_error;
   if (g_close (from_child[READ], err)==-1)
       goto raise_error;

  
   /* create new process using argv string vector. Set newly created
    * pipes as stdin and stdout of child process. Stderr output shall
    * be same as of parent process */
   if ((child=g_process_new (argv,
                             to_child[READ],
                             from_child[WRITE],
                             G_STDERR,
                             0,
                             err))==-1)
      goto raise_error;

   /* close ends of pipe that have been inherited
    * and shall be
resources of child process only */
   if (g_close (to_child[READ], err)==-1)
       goto raise_error;
   if (g_close (from_child[WRITE], err)==-1)
       goto raise_error;

   return child;

  raise_error:
   if (to_child[WRITE]!=-1)   g_close (to_child[WRITE],   NULL);
   if (to_child[READ]!=-1)    g_close (to_child[READ],    NULL);
   if (from_child[WRITE]!=-1) g_close (from_child[WRITE], NULL);
   if (from_child[READ]!=-1)  g_close (from_child[READ],  NULL);

   if (*in!=-1)    { g_close (*in, NULL);  *in=-1;  }
   if (*out!=-1)   { g_close (*out, NULL); *out=-1; }

   return -1;
}


Portable way for synchronizing with  child process:

Main function creating child and waiting for its termination
#include <glib-2.0/glib.h>

int
main (int argc, char* argv[])
{
   if (argc>1)
   {
      GError* err;
      gint timeout=100*1000; /* milliseconds */
      gint status;
      gint in;
      gint out;
      gproc child = 0;

      if (g_ios_init (&err)==-1)
      {
         g_warning ("%s", err->message);
         g_error_free (err);
         exit (1);
      }

      /* create child process given as command line parameters */
      if ((child=create_child (&argv[1], &in, &out, &err))==0)
      {
          g_warning ("%s", err->message);
          g_error_free (err);
          exit (1);
      } 

      {
         gchar c;
         for (c=0; c<127; ++c)
         {
            if (g_write (in, &c, sizeof (c), &err)==-1)
            {
               g_process_kill (child, G_SIGQUIT, NULL);
               g_process_free (child, NULL);

               g_warning ("%s", err->message);
               g_error_free (err);

               exit (1);
            }
       
            if (g_read (out, &c, sizeof (c), &err)==-1)
            {
               g_process_kill (child, G_SIGQUIT, NULL);
               g_process_free (child, NULL);

               g_warning ("%s", err->message);
               g_error_free (err);

               exit (1);
            }

            g_print ("received child %3d\n", c);
         }
      }

      switch (g_process_wait (child, &status, timeout, &err))
      {
      case -1:
         g_warning ("%s", err->message);
         g_error_free (err);
         g_process_kill (child, G_SIGQUIT, NULL);
         g_process_free (child, NULL);
         exit (1);
      case 0:
         g_warning ("reached timeout");
         g_process_kill (child, G_SIGQUIT, NULL);
         g_process_free (child, NULL);
         exit (1);
      default:
         g_print ("child terminated with status: %d\n", status);
         g_process_free (child, NULL);
      }
   }
   else
   {
      g_print ("uage: %s <command> \n", argv[0]);
      exit (1);
   }
}


Currently g_proces_kill only supports G_SIGQUIT signal in portable manner, but soon it will be extended to G_SIGINT and G_SIGTERM signals that can be send to other processes or process groups.

child process reading from  STDIN and writing to STDOUT
int
main (int argc, char* argv[])
{
   gint c;
   if (g_ios_init (&err)==-1)
   {
      g_warning ("%s", err->message);
      g_error_free (err);
      exit (1);
   }

   while (1)
   {
      if (g_read (G_STDIN, &c, sizeof(c), NULL)==-1)
         exit (1);

      c+=128;

      if (g_write (G_STDOUT, &c, sizeof(c), NULL)==-1)
         exit (1);

      if (c==255) exit (0);
   }
}


Asychronous IO can be done portable, too. This is an example from unit test shipped with gios source code:



portable asynchronous IO
#define CHK_ERR(err) if (err!=NULL) {\
    g_warning (" chk_err: %s(%d)/%d/%s\n", __FUNCTION__, __LINE__,  \   
                                           err->code, err->message);\
    cUnit_assert (err==NULL);\
}

#define POLL_CHUNK_SIZE (2<<20)

gchar*
tst_poll__parent (gpointer data)
{
   GError* err = NULL;
  
   gint  child          = -1;
   gint  to_child_pipe[2]  = {-1, -1};
   gint  to_parent_pipe[2] = {-1, -1};
  
   gint  to_child   = -1;
   gint  from_child = -1;

   char  arg_cmd [MAX_ARG_LEN+1];
   char* args[]={NULL,NULL,NULL,NULL,NULL,NULL,NULL};
 
   g_pipe (to_child_pipe,  &err); CHK_ERR (err);
   g_pipe (to_parent_pipe, &err); CHK_ERR (err);

   to_child   = g_dup (to_child_pipe[WRITE], FALSE, &err); CHK_ERR (err);
   from_child = g_dup (to_parent_pipe[READ], FALSE, &err); CHK_ERR (err);
   g_close (to_child_pipe[WRITE], &err);                   CHK_ERR (err);
   g_close (to_parent_pipe[READ], &err);                   CHK_ERR (err);
  
   g_snprintf (arg_cmd, MAX_ARG_LEN, "%d", TST_POLL);
  
   args[0] = _argv[0];
   args[1] =  arg_cmd;
   args[2] =  NULL;

   child=g_process_new (args,
                        to_child_pipe[READ],
                        to_parent_pipe[WRITE],
                        G_STDERR,
                        0,
                        &err);
   CHK_ERR (err);
   cUnit_assert (child>0);

   g_close (to_child_pipe[READ],   &err); CHK_ERR (err);
   g_close (to_parent_pipe[WRITE], &err); CHK_ERR (err);
  
   G_IO_NONBLOCKING (to_child,   &err); CHK_ERR (err);
   G_IO_NONBLOCKING (from_child, &err); CHK_ERR (err);

   {
      GPollFD ufds[2];
      char buf[POLL_CHUNK_SIZE];

      gint i;
      gint sent=0;
      gint recv=0;

      for (i=0; i<POLL_CHUNK_SIZE; ++i)
     buf[i] = 'A';


      ufds[0].fd      = to_child;
      ufds[0].events  = G_IO_OUT;
      ufds[0].revents = 0;
      ufds[1].fd      = from_child;
      ufds[1].events  = G_IO_IN;
      ufds[1].revents = 0;

      while (sent!=sizeof(buf) && recv!=sizeof(buf))
      {
     gint n=0;

     // wait infinite time, until ready for read or write
     gint nevents=g_poll (ufds, 2, -1, &err);

     cUnit_assert (nevents>0);
     CHK_ERR (err);
     
     cUnit_assert ( ! (ufds[0].revents & G_IO_IN) );
     cUnit_assert ( ! (ufds[0].revents & G_IO_ERR) );
     cUnit_assert ( ! (ufds[0].revents & G_IO_HUP) );
     cUnit_assert ( ! (ufds[0].revents & G_IO_NVAL) );
     cUnit_assert ( ! (ufds[0].revents & G_IO_PRI) );

     if (ufds[0].revents & G_IO_OUT)
     {
        sent+=g_write (to_child,
               &buf[sent],
               sizeof(buf)-sent,
               &err);
        CHK_ERR (err);
     }
     
     cUnit_assert ( ! (ufds[1].revents & G_IO_OUT));
     cUnit_assert ( ! (ufds[1].revents & G_IO_PRI));
     cUnit_assert ( ! (ufds[1].revents & G_IO_ERR));
     cUnit_assert ( ! (ufds[1].revents & G_IO_HUP));
     cUnit_assert ( ! (ufds[1].revents & G_IO_NVAL));

     if (ufds[1].revents & G_IO_IN)
     {
        recv+=g_read (from_child,
               &buf[recv],
               sizeof(buf)-recv,
               &err);
        CHK_ERR (err);
     }
      }

      // verify characters
      for (i=0; i<sizeof(buf); ++i)
     cUnit_assert (buf[i]=='A');
   }

   {
       char c=0;
       g_write (to_child, &c, sizeof(c), &err);  CHK_ERR (err);
   }

   G_IO_BLOCKING (to_child, &err);   CHK_ERR (err);
   G_IO_BLOCKING (from_child, &err); CHK_ERR (err);


   g_close (to_child, &err);      CHK_ERR (err);
   g_close (from_child, &err);    CHK_ERR (err);

   // child must terminate with 0
   {
      gint stat=0;
      gint ret=g_process_wait (child, &stat, 10*1000, NULL);
      cUnit_assert (stat==0);
   }
  
   g_process_free (child, NULL);

   return NULL;
}


Author: F.Rehberger (frehberg at cs.tu-berlin.de) ,  (C) 2002


CSSValid CSS!Valid HTML 4.0!

Frank Rehberger
created 3 Apr 2004;
Last updated 2004/04/03