testMakeClean.main.c
/****************************************************************************************************/
/*                                                                                                  */
/* testMakeClean.main.c:                                                                            */
/*                                                                                                  */
/****************************************************************************************************/

/****************************************************************************************************/
/*                                                                                                  */
/*     Copyright (C) 2004 Joerg Kunze                                                               */
/*                                                                                                  */
/*     This file is part of siliconBrain.                                                           */
/*                                                                                                  */
/*     siliconBrain is free software; you can redistribute it and/or modify                         */
/*     it under the terms of the GNU General Public License as published by                         */
/*     the Free Software Foundation; either version 2 of the License, or                            */
/*     (at your option) any later version.                                                          */
/*                                                                                                  */
/*     siliconBrain is distributed in the hope that it will be useful,                              */
/*     but WITHOUT ANY WARRANTY; without even the implied warranty of                               */
/*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                */
/*     GNU General Public License for more details.                                                 */
/*                                                                                                  */
/*     You should have received a copy of the GNU General Public License                            */
/*     along with this program; if not, write to the Free Software                                  */
/*     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA                    */
/*                                                                                                  */
/****************************************************************************************************/

/* some parts are taken from glibc-2.3.2/posix/execvp */

/* Copyright (C) 1991,92,95,96,97,98,99,2002 Free Software Foundation, Inc.
   This file is part of the GNU C Library.

   The GNU C Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   The GNU C Library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with the GNU C Library; if not, write to the Free
   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
   02111-1307 USA.  */

#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <utime.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <pwd.h>
#include <sys/stat.h>
#include <sys/wait.h>

#include "siliconBrainLib"
#include "siliconBrainOption"

#include "stringSearch"

static String siliconBrainRelease       = c( "$siliconBrainRelease: 0.2.3 $"                               );
static String siliconBrainRcsIdentifier = c( "$Id: testMakeClean.main.c,v 1.6 2004/12/14 23:31:26 joerg Exp $" );
static String siliconBrainSaveStamp     = c( "$siliconBrainSaveStamp: 2004/12/14 22:14:50, Joerg Kunze$"   );

static String programName               = c( "testMakeClean: " );

/****************************************************************************************************/
/*                                                                                                  */
/* const:                                                                                           */
/*                                                                                                  */
/****************************************************************************************************/
static const int failure = -1;
static const int success =  0;

static const double second = 1.0;
static const double minute = 60.0;
static const double hour   = 60.0*60.0;
static const double day    = 24.0*60.0*60.0;

#define fileToTestFreshness "temporary/isInitialized"

/****************************************************************************************************/
/*                                                                                                  */
/* infoPrint:                                                                                       */
/*                                                                                                  */
/****************************************************************************************************/
#define infoPrint( message ) print( programName ); println( (message) );

/****************************************************************************************************/
/*                                                                                                  */
/* command:                                                                                         */
/*                                                                                                  */
/****************************************************************************************************/
typedef struct {
   String  command;
   bool    script;
   bool    scriptTested;
   char   *arguments[];
} CCommand;

typedef char **Command;

#define inFile  STDIN_FILENO
#define outFile STDOUT_FILENO
#define errFile STDERR_FILENO

#define command( programName, ... ) (( char *[]){ toCa( (programName) ), __VA_ARGS__, 0 })\

/****************************************************************************************************/
/*                                                                                                  */
/* which:                                                                                           */
/*                                                                                                  */
/****************************************************************************************************/
String which( String shortName ) {

   /*--------------------------------------------------------------------------------------*/
   /* this is a "which" implementation. It is partly inspired by: glibc-2.3.2/posix/execvp */
   /*                                                                                      */
   /* We don't use execvp(), because we like to remember the found path and the information*/
   /* whether we used the shell (script-exec) to start the executable.                     */
   /*--------------------------------------------------------------------------------------*/
   char *path = getenv( "PATH" );

   /*-------------------------------------------------------------------------------------------*/
   /* if the PATH variable is not set, we read the corresponding System Configuration Paramater */
   /* (in this case a String Valued Paramater). The default search path is the current          */
   /* directory followed by the path `confstr' returns for `_CS_PATH'.                          */
   /*-------------------------------------------------------------------------------------------*/
   if( !path ) {
      int csPathLength;
      path = alloca( csPathLength = confstr( _CS_PATH, 0, 0 ) + 1 );      // the "+ 1" is for the extra ':'
      confstr( _CS_PATH, path + 1, csPathLength );                        // the "+ 1" is for the extra ':'
      path[ 0 ] = ':';
   }

   String directory = { .begin = path, .length = 0, .capacity = 0 };
   String tilde;

   String programName = stringNull;

   char *directoryPrefix;

   /*------------------------------------------------------------*/
   /* try for each of the ':' seperated parts of the path string */
   /*------------------------------------------------------------*/
   while( *directory.begin ) {

      /*----------------------------------------------------------------------------------------*/
      /* at the begining of the path string (directory.begin == path) the ':' is *not* skipped, */
      /* and so defines an empty directory: the current directory.                              */
      /* We skipp only one ':', so that two consequtive ':'s define again the empty dir.        */
      /* A ':' at the very end will result in an empty string, and quite again is the empty dir.*/
      /*----------------------------------------------------------------------------------------*/
      if( *directory.begin == ':' && directory.begin != path ) ++directory.begin;

      directory.length = strcspn( directory.begin, ":" ); // strchrnul() is another idea here

      if( !directory.length ) {
         copy_a( programName, shortName );
      }
      else if( *directory.begin == '~' ) {

         tilde.begin  = directory.begin + 1;
         tilde.length = strcspn( tilde.begin, "/" );

         directory.begin  += (tilde.length + 1);
         directory.length -= (tilde.length + 1);

         char *userName;

         if( tilde.length ) {
            userName = toCa( tilde );
         }
         else {
            userName = getenv( "LOGNAME" );
         }

         struct passwd  passwd, *resultPointer;
         size_t         bufferLength = 421;
         char          *buffer       = alloca( bufferLength );

         while( 0 != getpwnam_r( userName, &passwd, buffer, bufferLength, &resultPointer ) )
            if( ERANGE == errno )
               buffer = alloca( bufferLength <<= 1 );
            else {
               resultPointer = 0;
               break;
            }

         directoryPrefix = ( resultPointer && passwd.pw_dir ) ? passwd.pw_dir : "<no user>";

         collect_a( programName, string( directoryPrefix ), directory, directory.begin[ directory.length -1 ] == '/' ? c( "" ) : c("/"), shortName );
      }
      else {
         collect_a( programName, directory, directory.begin[ directory.length -1 ] == '/' ? c( "" ) : c("/"), shortName );
      }

      if( access( programName.begin, X_OK ) == 0 ) {
         break;
      }

      directory.begin += directory.length;
   }

#if 0
   // TODO: detect a ENOEXEC before forking, so that we can cache this information
   // TODO: detect the script interptreter by examning the #!/bin/bash line an remember this information in the  Command struct

   script_execute (const char *file, char *const argv[])
      {
         /* Count the arguments.  */
         int argc = 0;
         while (argv[argc++])
            ;

         /* Construct an argument list for the shell.  */
         {
            char *new_argv[argc + 1];
            new_argv[0] = (char *) _PATH_BSHELL;
            new_argv[1] = (char *) file;
            while (argc > 1)
               {
                  new_argv[argc] = argv[argc - 1];
                  --argc;
               }

            /* Execute the shell.  */
            __execve (new_argv[0], new_argv, __environ);
         }
      }

   __execve (startp, argv, __environ);

   if (errno == ENOEXEC)
      script_execute (startp, argv);
#endif

   return programName;
}

/****************************************************************************************************/
/*                                                                                                  */
/* executeRaw:                                                                                      */
/*                                                                                                  */
/****************************************************************************************************/
pid_t executeRaw( Command arguments, int in, int out, int err ) {

   pid_t pid = fork();

   if( pid == 0 ) {
      dup2( in , STDIN_FILENO  );
      dup2( out, STDOUT_FILENO );
      dup2( err, STDERR_FILENO );

      // TODO: close unused fileDescriptors like in, out ... if different from STDIN_FILENO or not used ends of pipes
      //       or totally different files only usefull in the paraents process.
      // there seems to be a close-on-exec flag settable by fcntl()

      execve( arguments[ 0 ], arguments, environ );
      _exit( EXIT_FAILURE );
   }

   return pid;
}

/****************************************************************************************************/
/*                                                                                                  */
/* execute:                                                                                         */
/*                                                                                                  */
/****************************************************************************************************/
int execute( Command arguments, int in, int out, int err ) {

   int status;

   waitpid( executeRaw( arguments, in, out, err ), &status, 0 );

   return status;
}

/****************************************************************************************************/
/*                                                                                                  */
/* testMakeClean:                                                                                   */
/*                                                                                                  */
/****************************************************************************************************/
void testMakeClean() {

   infoPrint( c( "do the clean test" ) );

   /*------------------------------------*/
   /* touch the file: create a new mtime */
   /*------------------------------------*/
   utime( fileToTestFreshness, 0 );

   String commandName = which( c( "make" ) );

   int nullFile = open( "/dev/null", O_RDONLY );

   Command makeAll = command( commandName, "all" );

   int status = 0;

   if( status = execute( command( commandName, "clean" ), inFile, nullFile, nullFile ) ) {
      infoPrint( c( "error in make clean" ) );
      exit( WEXITSTATUS( status ) );
   }

   if( status = execute( makeAll, inFile, nullFile, nullFile ) ) {
      infoPrint( c( "error in make clean" ) );
      exit( WEXITSTATUS( status ) );
   }

   int mypipe[ 2 ];
   pipe( mypipe );

   pid_t pid = executeRaw( makeAll, inFile, mypipe[ 1 ], nullFile );

   close( mypipe[ 1 ] );

   CommandReturnCode   returnCode = commandOk;

   stringSearchOptions options = {
      .expression = "Nothing to be done"
   };

   {
      FileReaderContext context;
      context.openCommand  = (void *)stringSearchOpen;
      context.closeCommand = (void *)stringSearchClose;
      context.openFile     = (void *)0;
      context.closeFile    = (void *)0;
      context.userCommand = (UserCommand)&stringSearch;
      returnCode = pipeReader( &context, &options, mypipe[ 0 ] );
   }

   waitpid( pid, &status, 0 );

   if( status ) {
      infoPrint( c( "error in second make all" ) );
      exit( WEXITSTATUS( status ) );
   }

   exit( returnCode != commandOk );
}

/****************************************************************************************************/
/*                                                                                                  */
/* main:                                                                                            */
/*                                                                                                  */
/****************************************************************************************************/
extern int main( int argc, char *argv[] ) {

   infoPrint( siliconBrainRelease       );
   infoPrint( siliconBrainRcsIdentifier );
   infoPrint( siliconBrainSaveStamp     );

   struct stat isInitializedFile;
   if(
      stat( fileToTestFreshness, &isInitializedFile ) != success     ||
      difftime( time( 0 ), isInitializedFile.st_mtime ) > day
   )
      testMakeClean();

   return 0;
}

/*
$Log: testMakeClean.main.c,v $
Revision 1.6  2004/12/14 23:31:26  joerg
published for new release 0.2.3

Revision 1.5  2004/12/14 23:17:05  joerg
published for new release 0.2.2

Revision 1.4  2004/12/14 22:20:09  joerg
pipe output of make command into stringSearch c-function
without creating a process for stringSearch. The later is used as a C-function command, not as a main.

Revision 1.3  2004/12/12 11:39:39  joerg
correct pipe and process creation. Now it is complete (but not factored out).

Revision 1.2  2004/11/29 00:14:34  joerg
incorporate a pipe to catch make's output

Revision 1.1  2004/11/09 00:47:55  joerg
targets, which use siliconbrain tools like "generate" should depend on it. First version of a "make clean;
make all" sequence tester. Everything written in C !!!!!!!!!!!!!!!!!!! (like collect_a, command and execute).

*/