Bug 2206

Summary: Java Dependencies for RPM
Product: [Retired] Red Hat Linux Reporter: kestes
Component: rpmAssignee: Jeff Johnson <jbj>
Status: CLOSED CURRENTRELEASE QA Contact:
Severity: low Docs Contact:
Priority: low    
Version: 5.2   
Target Milestone: ---   
Target Release: ---   
Hardware: All   
OS: Linux   
Whiteboard:
Fixed In Version: Doc Type: Bug Fix
Doc Text:
Story Points: ---
Clone Of: Environment:
Last Closed: 1999-06-13 03:16:40 UTC Type: ---
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: --- Target Upstream Version:
Embargoed:

Description kestes 1999-04-15 23:53:55 UTC
I am sending you a copy of my java dependency program.
It is mostly done and tested.  It will be used to
track JAVA dependencies much as shared object dependencies
are currently tracked using LDD. I will do the integration
into RPM next and add testsuites.  I am just sending this
to you for safe keeping incase I falloff the net or
something, it would be a shame to loose all this work.
You do not need to add this into the RPM CVS archive at
this time, just hang onto the mail message.

I am also working on PERL, TCL and other dependency
programs.

Ken Estes


/*
   Simple progam for pullng all the referenced java classes
out of a
   class file.  Java files are supposed to be platform
independent, so
   this program should work on all platforms.  This code is
based on
   the information found in:

       "Java Virtual Machine" by Jon Meyer & Troy Downing.
          O'Reilly & Associates, INC. (First Edition, March
1997)
          ISBN: 1-56592-194-1

   Jonathan Ross, Ken Estes
   Mail.com
 */


/*
   Remember that:

   JAR consists of a zip archive, as defined by PKWARE,
containing
   a manifest file and potentially signature files, as
defined in
   the Manifest and Signature specification.  So we use
infozip's
   'unzip -p' found at http://www.cdrom.com/pub/infozip/.

   Additional documentation, about this fact, at:


http://java.sun.com/products/jdk/1.1/docs/guide/jar/jarGuide.html

http://java.sun.com/products/jdk/1.2/docs/guide/jar/jarGuide.html


http://java.sun.com/products/jdk/1.1/docs/guide/jar/manifest.html

http://java.sun.com/products/jdk/1.2/docs/guide/jar/manifest.html

*/


#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>

/*---------typedefs---------*/


/* The symbol table is a waste of memory..
   but it's easy to code! */

typedef struct {
  short poolSize;
  char **stringList;
  short *classRef;
  short *typeRef;
} symbolTable_t;


/*---------Global Variables, in all caps---------*/

/*name of this program*/
char *PROGRAMNAME=0;

/*name of the current class file*/
char *FILENAME=0;

/*the name of the last class file seen*/
char *CLASSNAME=0;

/*arguments chosen*/
int ARG_PROVIDES=0;
int ARG_REQUIRES=0;
int ARG_RPMFORMAT=0;
int ARG_KEYWORDS=0;

/*keywords found in class file*/
char *KEYWORD_PROVIDES=0;
char *KEYWORD_CONFLICTS=0;
char *KEYWORD_REQUIRES=0;
char *KEYWORD_VERSION=0;


/*--------- declare all functions ---------*/

void usage (void);
void outofmemory(void);
void die(char *format, ...);
size_t my_fread(void *ptr, size_t size, size_t nitems, FILE
*stream);
void check_range(short value, short poolSize);
char *is_lower_equal (char *string, char *pattern);
int findJavaMagic (FILE *fileHandle);
int dumpClassName(char *pSomeString, char terminator);
void dumpRefType(char *pSomeString);
void dumpRequires(symbolTable_t *symbolTable);
void genSymbolTable (FILE *fileHandle, symbolTable_t
*symbolTable);
void findClassName  (FILE *fileHandle, symbolTable_t
*symbolTable);
void freeSymbolTable (symbolTable_t *symbolTable);

/*--------- functions ---------*/

void
usage (void)
{
  printf("NAME:\n\tjavadeps - Examine Java class files
and\n"
	 "\t\treturn information about their dependencies.\n\n");
  printf("USAGE:\n");
  printf("\t javadeps { --provides | --requires } \n"
	 "\t\t   [--rpmformat] [--keywords] \n"
	 "\t\t     [--] classfile-name ... \n\n"
	 "\t javadeps [--help]\n\n");
  printf("\n\n");
  printf("OPTIONS:\n\n");
  printf("--requires  For each class files listed in the
arguments,\n"
	 " -r         print the list of class files that would
be\n"
	 "            required to run these java programs.  This
does not \n"
	 "            include anyting instantiated by
reflection.\n\n");
  printf("--provides  For each class files listed in the
arguments, \n"
	 " -p         Print the fully qualified java classes,\n"
	 "            that they provide.\n\n");
  printf("--rpmformat format the output to match that used
by RPM's \n"
	 " -F         (Red Hat Package Manager) dependency
analysis  \n"
	 "            database. The default is not
--rpmformat.\n\n");
  printf("--keywords  Make use of any keywords embeded in
the classfile.\n"
	 " -k         The default is not --keyword.\n\n");
  printf("--help      Display this page and exit.\n");
  printf("--          This stops the processing of
arguments, making it \n"
	 "            easier for users to have filenames like
'--keyword',\n"
	 "            without the command line parser getting
confused.\n\n");
  printf("DESCRIPTION:\n\n");
  printf("List the dependencies or the fully qualified name,
of the \n"
	 "classfiles listed on the command line.  \n");
  printf("\n\n");
  printf("If any of the class file names is '-' then <stdin>
will be read\n"
	 "instead of reading from a file.  It is assumed that when
run \n"
	 "with '-', this program is in a pipeline preceeded by the
\n"
	 "command 'unzip -p filename.jar' so that <stdin> may
contain\n"
	 "the contents of several classfiles.\n");
  printf("\n\n");
  printf("If --keyword is specified then the following
strings are \n"
	 "searched for (case insensitive) in the class file string
table\n"
	 "and, if found, are used to change the dependencies
accordingly.\n\n"
	 "Keyword List:\n\n"
	 "'$Revision: '     This RCS/CVS compatible keyword is
assumed to \n"
	 "                  contain the version number of the class
file \n"
	 "                  it is found in.  Care should be taken
with this \n"
	 "                  option as RPM's notion of which version
is later\n"
	 "                  may not corrispond with your own,
especially\n"
	 "                  if you use branches.  This keyword only
effects\n"
	 "                  the output of --provides.\n\n"
	 "'RPM-Provides: '  This string lists additinal capabilites
provided\n"
	 "                  by the java class.  This keyword only
effects \n"
	 "                  the output of --provides.\n\n"
	 "'RPM-Conflicts: ' This string lists capabilites which
conflict\n"
	 "                  with the current java class.  This
keyword\n"
	 "                  only effects the output of
--provides.\n"
	 "                      (not implemented yet )\n\n"
	 "'RPM-Requires: '  This string lists additional
requirements of\n"
	 "                  the java class.  This keyword only
effects the \n"
	 "                  output of --requires.\n\n");
  exit(-1);
}


void outofmemory(void) {

  /* Its doubtful we could do a printf if there is really a
memory
    issue but at least halt the program */

  fprintf(stderr, "Could not allocate memory");
  exit(-1);
}


void die(char *format, ...) {
  /* Most errors are fatal.
     This function throws a fatal error and
     accepts arguments like printf does*/

  char  *newformat, *newmsg;
  va_list ap;

  if ( !(newformat = malloc(1024)) || !(newmsg =
malloc(1024)) )
    outofmemory();

  /* Rewrite format line, to include additional
information.  The
     format line we chose depends on how much information is
availible
     at the time of the error.  Display the maximum ammount
of
     information. */

  /* notice the FILENAME is useless for jar files.  We would
want to
     print the name of the classfile which caused the
error.  That
     is hard since we only know that name when we are done
parsing
     the file, and most errors will occur before that.*/

  va_start(ap, format);

  if ( (!FILENAME) ) {

    sprintf (newformat, "\n%s: %s",
	     PROGRAMNAME, format);

  } else if ( (FILENAME) && (!CLASSNAME) ) {

    sprintf (newformat, "\n%s: Java classfile: %s, %s",
	     PROGRAMNAME, FILENAME, format);

  } else if (CLASSNAME) {
    sprintf (newformat, "\n%s: Java classfile: %s,
classname: %s, %s",
	     PROGRAMNAME, FILENAME, CLASSNAME, format);
  }

  vsprintf (newmsg, newformat, ap);

  /* print error to where it needs to go:
	 stdout, stderr, or syslog
  */

  fprintf(stderr, newmsg);

  free(newformat);
  free(newmsg);

  exit(-1);
}


/* wrap fread for safety.  It is a fatal error to get an
unexpected
   EOF inside a class file. */

size_t my_fread(void *ptr, size_t size, size_t nitems, FILE
*stream) {
  size_t rc=0;
  /*these variables are helpful in the debugger*/
  int eof=0;
  int error=0;


  rc = fread(ptr, size, nitems, stream);
  if ( (size!=0) && (rc == 0) ) {
    eof = feof(stream);
    error = ferror(stream);
    die("Error reading from file, or unexpected EOF\n");
  }
  return ;
}


void check_range(short value, short poolSize) {

  if (value > poolSize) {
    die("Value: %d, is out of range of the constant pool\n",
	value);
  }
  return ;
}



/* If lower case conversion of string is equal to pattern
return a
   pointer into string, just after the match.  If the string
does not
   patch the pattern the null pointer is returned.  This
does not
  change string.

   This is similar to strcasecmp, but I expect my patterns
to be a
   prefix of my strings. */

char
*is_lower_equal (char *string, char *pattern)
{

  while ( (tolower(*string) == *pattern) &&
	  *string && *pattern )  {
    string++;
    pattern++;
  }

  if ( *pattern == 0 ) {
    return string;
  }

  return NULL;
}


/*
   Read fileHandle until we find the next instance of the
Java
   Classfile magic number indicating a java file or find EOF
or
   fileread error.

   Return 1 on success 0 on failure.  */

#define mod4(num) ( (num) & 3 )


int findJavaMagic (FILE *fileHandle)
{
  int offset=0;
  int foundMagic = 0;
  size_t rc;

  /* what were looking for */
  unsigned char magicInt[4] = {0xCA, 0xFE, 0xBA, 0xBE};
  /*the hex reads in decimal: 202 254 186 190 */

  /* a circular buffer indicating the last few bytes we read
*/
  unsigned char buffer[4] = {0};

  foundMagic = 0;
  while( !foundMagic ) {

      rc = fread(&buffer[offset], 1, 1, fileHandle);
      if ( !rc ) {

	/* Either this was not a java file or we were given a jar
file
           and have already found the last java file in
it.*/

	if ( feof(fileHandle) ) {
	  return 0;
	}

	if ( ferror(fileHandle) ) {
	  die ("Error reading character from file.\n");
	};

      }

      /* offset points to the most recent char we read so
offest+1
         points to the oldest char we saved. */

      foundMagic = (
		    (magicInt[0] == buffer[mod4(offset+1)]) &&
		    (magicInt[1] == buffer[mod4(offset+2)]) &&
		    (magicInt[2] == buffer[mod4(offset+3)]) &&
		    (magicInt[3] == buffer[mod4(offset+0)]) &&
		    1
		    );

      offset = mod4(offset+1);

    } /*end while*/

  return foundMagic;
}

#undef mod4


/*  Print a properly formatted java class name, and returns
the length
    of the class string .  */

int
dumpClassName(char *pSomeString, char terminator)
{
  int chars =0;
  /* For each char in the class name format it and print it
to stdout.
     No class terminator is written in this function.
     All class types start with a 'L' and and end with a
';'. */

  if(ARG_RPMFORMAT) {
    printf("java(");
  }

  while (1) {
	if (*pSomeString == terminator) {
	/* normal exit from loop when end of class reached */
	if(ARG_RPMFORMAT) {
	  printf(")");
	}
	return chars;
	} else if (*pSomeString == '\0' ) {
	/* fatal error, exit from loop when end of string reached
and
           terminator is not \0 */
	die("Classname does not terminate with ';'\n");
	} else if (*pSomeString == '/' ) {
	  /* convert '/' to '.' */
	  printf(".");
	} else {
	  /* print this char and keep going, */
	  printf("%c",*pSomeString);
	} /*end switch*/
      pSomeString++;
      chars++;
      } /*end while*/
  /*not reached*/
  return chars;
}


/* Parse out just the class names from a java type and moves
the string
   pointer to the end of the string. */

void
dumpRefType(char *string)
{
  /* All class types start with a 'L' and and end with a
';'. We want
     everyting in between.  There might be more then one per
string
     like (params for a method call) */

  string = strchr(string, 'L');
  while (string) {
    string++;
    string = (string) +  dumpClassName(string, ';');
    printf("\n");
    string = strchr(string, 'L');
  }

  return ;
}


/* Print out the classes referenced in the symbol table */

void
dumpRequires(symbolTable_t *symbolTable) {
  int tem;
  int ref = 0;

  for(tem=1; tem < symbolTable->poolSize; tem++ ) {

    /* dump all the classes in the const table. */
    ref = symbolTable->classRef[tem];
    if(ref) {
      char *string = symbolTable->stringList[ref];
      if( !*string ) {
	die("class num: %d, referenced string num: %d, "
	    "which is null.\n",
	    tem, ref);
      }
      if ( string[0] == '[' ) {
	/*
	  This is an array. We need to ingore
	  strings like:
	       '[B'
	       '[[B'
	       '[[I'

	  This hack leaves blank lines in the output
	  when a string not containing a class is
	  sent to dumpRefType.
	*/
	string = strchr(string, 'L');
	if (string) {
	  dumpRefType(string);
	}
      } else {
	dumpClassName(string, '\0');
      }
      printf("\n");
    }

    /* dump all the references */
    ref = symbolTable->typeRef[tem];
    if (ref) {
      char *string = symbolTable->stringList[ref];
      if ( !*string ) {
	die("type num: %d, referenced string num: %d, "
	    "which is null.\n",
	    tem, ref);
      }
      /* this is a java type... parr out the class names */
      dumpRefType(string);
    }

  } /*end for*/

  return ;
}


/* Read a java class files symbol table into memory.
		- also  -
   Find the proper name of the current Java Class file.
   Print it regardless of: --provides | --requires
*/


void genSymbolTable (FILE *fileHandle, symbolTable_t
*symbolTable)
{
  char ignore[10];
  int i;


  /* We are called just after fileHandle saw the magic
number, seek a
     few bytes in to find the poolsize */

  my_fread(&ignore, 4, 1, fileHandle);

  my_fread(&(symbolTable->poolSize), 2, 1, fileHandle);

  /* new the tables */

  symbolTable->stringList = (char**)
calloc(symbolTable->poolSize,
					    sizeof(char*));
  if(!symbolTable->stringList){
    outofmemory();
  }

  symbolTable->classRef = (short*)
calloc(symbolTable->poolSize,
					  sizeof(short*));
  if(!symbolTable->classRef){
    outofmemory();
  }

  symbolTable->typeRef = (short*)
calloc(symbolTable->poolSize,
					 sizeof(short*));
  if(!symbolTable->typeRef){
    outofmemory();
  }

  /* zero 'em all out. */
  for(i=0; i < symbolTable->poolSize; i++) {
    symbolTable->stringList[i] = NULL;
    symbolTable->classRef[i] = 0;
    symbolTable->typeRef[i] = 0;
  }


  /* for the number of entries
       (it starts at 1)
  */

  for(i=1; i < symbolTable->poolSize; i++) {
    unsigned short type = 0;
    unsigned short value = 0;
    unsigned char tag = 0;

      /* read the type of this entry  */

      my_fread(&tag, 1, 1,fileHandle);
      switch(tag) {
	case 1: /* master string pool. */
	  {
	    /* record all these strings */
		char *someString;
		unsigned short length = 0;

		/* I am not sure if these strings must be null
		   terminated.  I termiante them to be safe. */

		my_fread(&length, 2, 1, fileHandle);
		someString = (char*) malloc(length+1);
		if(!someString){
		  outofmemory();
		}
		my_fread(someString, length, 1, fileHandle);
		someString[length]=0;
		symbolTable->stringList[i] = someString;

		if (ARG_KEYWORDS) {
		  char *ptr=0;

		  ptr = is_lower_equal(someString, "rpm-requires: ");
		  if(ptr){
		    KEYWORD_REQUIRES=ptr;
		  }
		  ptr = is_lower_equal(someString, "rpm-provides: ");
		  if(ptr){
		    KEYWORD_PROVIDES=ptr;
		  }
		  ptr = is_lower_equal(someString, "rpm-conflicts: ");
		  if(ptr){
		    KEYWORD_CONFLICTS=ptr;
		  }
		  ptr = is_lower_equal(someString, "$revision: ");
		  if(ptr){
		    KEYWORD_VERSION=ptr;
		    /* terminate the string before " $" */
		    ptr = strchr(KEYWORD_VERSION, ' ');
		    if (ptr) {
		      *ptr = 0;
		    }
		  }
		}
		break;
	  }
      case 2:	/* unknow type!! */
	  die("Unknown type in constant table. "
	      "Entry: %d. \n", i);
	  break;
	case 3:	/* int */
	  my_fread(&ignore, 4, 1, fileHandle);
	  break;
	case 4: /* float */
	  my_fread(&ignore, 4, 1, fileHandle);
	  break;
	case 5: /* long (counts as 2) */
	  my_fread(&ignore, 8, 1, fileHandle);
	  i++;
	  break;
	case 6: /* double (counts as 2) */
	  my_fread(&ignore, 8, 1, fileHandle);
	  i++;
	  break;
	case 7: /* Class */
	  my_fread(&value, 2, 1, fileHandle);
	  /* record which const it's referencing */
	  check_range(value, symbolTable->poolSize);
	  symbolTable->classRef[i]=value;
	  break;
	case 8: /* String */
	  my_fread(&ignore, 2, 1, fileHandle);
	  break;
	case 9: /* field reference */
	  my_fread(&ignore, 4, 1, fileHandle);
	  break;
	case 10: /* method reference */
	  my_fread(&ignore, 4, 1, fileHandle);
	  break;
	case 11: /* interface method reference */
	  my_fread(&ignore, 4, 1, fileHandle);
	  break;
	case 12: /* constant name/type */
	  my_fread(&ignore, 2, 1, fileHandle);
	  my_fread(&type, 2, 1, fileHandle);
	  /* record the name, and the type it's referencing. */
	  check_range(type, symbolTable->poolSize);
	  symbolTable->typeRef[i]=type;
	  break;
	default:
	  die("Unknown tag type: %d.\n",
	      "Entry: %d. \n", tag, i);
	  break;
      }
  }

  return ;
}


/*
   Find the proper name of the current Java Class file.
   Print it regardless of: --provides | --requires
*/

void
findClassName (FILE *fileHandle, symbolTable_t *symbolTable)
{
  char ignore[10];
  unsigned short type = 0;
  unsigned short class = 0;

  /* seek a little past the end of the table */

  my_fread(&ignore, 2, 1, fileHandle);

  /* read the name of this classfile */

  my_fread(&type, 2, 1, fileHandle);
  class = symbolTable->classRef[type];
  if( !class ||
      !symbolTable->stringList[class] ) {
      die("Couln't find class: %d, provided by file.\n",
class);
  }
  CLASSNAME=symbolTable->stringList[class];
  dumpClassName(symbolTable->stringList[class], '\0');
  if(KEYWORD_VERSION){
    printf("=%s", KEYWORD_VERSION);
  }
  printf("\n");

  return ;
}





void freeSymbolTable (symbolTable_t *symbolTable)
{
  int i;

  for(i=1; i < symbolTable->poolSize; i++) {
    if( symbolTable->stringList[i] ) {
      free(symbolTable->stringList[i]);
      symbolTable->stringList[i] = 0;
    }
  }

  free(symbolTable->stringList);
  symbolTable->stringList=0;

  free(symbolTable->classRef);
  symbolTable->classRef=0;

  free(symbolTable->typeRef);
  symbolTable->typeRef=0;

  free(symbolTable);
  symbolTable=0;

  return ;
}


/* process each file,
   must be called directly after finding
   the magic number.
*/

void processJavaFile (FILE *fileHandle) {
  symbolTable_t symbolTable= {0};

  genSymbolTable(fileHandle, &symbolTable);
  findClassName(fileHandle, &symbolTable);

  if(ARG_REQUIRES) {
    dumpRequires(&symbolTable);
    if (KEYWORD_REQUIRES) {
      printf(KEYWORD_REQUIRES);
    }
  } else {
    if (KEYWORD_PROVIDES){
      printf(KEYWORD_PROVIDES);
    }
    if (KEYWORD_CONFLICTS){
      printf(KEYWORD_CONFLICTS);
    }
  }

  freeSymbolTable(&symbolTable);

  return ;

}


int
main(int argc, char **argv)
{
  FILE *fileHandle;
  int i = 0;
  int rc = 0;
  int foundMagic=0;

  PROGRAMNAME=argv[0];

  if(argv[1] == NULL) {
    usage();
  }

  /* parse arguments which are not filenames*/

  for (i = 1; argv[i] != NULL; i++) {

    if (0) {
      /*
	 First entry a dummy to get the
	 other entries to align correctly
      */
      ;
    } else if ( !strcmp("-p",argv[i]) ||
!strcmp("--provides",argv[i]) ) {
      ARG_PROVIDES = 1;
    } else if ( !strcmp("-r",argv[i]) ||
!strcmp("--requires",argv[i]) ) {
      ARG_REQUIRES = 1;
    } else if ( !strcmp("-h",argv[i]) ||
!strcmp("--help",argv[i]) ||
		!strcmp("-?",argv[i]) ) {

      usage();
    } else if ( !strcmp("-F",argv[i]) ||
!strcmp("--rpmformat",argv[i]) ) {
      ARG_RPMFORMAT=1;
    } else if ( !strcmp("-k",argv[i]) ||
!strcmp("--keywords",argv[i]) ) {
      ARG_KEYWORDS=1;
    } else if ( !strcmp("--",argv[i]) ) {
      i++;
      break;
    } else {
      /* we do not recognize the argument, must be a
filename*/
      break;
    }
  } /*end for arguments which are not filenames*/

  /* check arguments for consistancy */

  if ( !ARG_PROVIDES && !ARG_REQUIRES ) {
    die ("Must specify either --provides or --requires.\n");
  }

  if ( ARG_PROVIDES && ARG_REQUIRES ) {
    die ("Can not specify both --provides and
--requires.\n");
  }

  if(argv[i] == NULL) {
    die ("Must specify Java class files.\n");
  }

  /* parse arguments which are filenames.  */

  for (0; argv[i] != NULL; i++) {

    /*open the correct file and process it*/

    if ( !strcmp("-", argv[i]) ) {
      /* use stdin, might be a jar file */
      fileHandle = stdin;
      FILENAME = "<stdin>";

      foundMagic = findJavaMagic(fileHandle);
      while (foundMagic) {
	processJavaFile(fileHandle);
	foundMagic = findJavaMagic(fileHandle);
      }
    } else {
      /* Open a disk file*/
      fileHandle = fopen(argv[i], "r");
      if( fileHandle == 0 ) {
	die ("Could not open file: %s.\n", argv[i]);
      }
      fileHandle = fileHandle;
      FILENAME = argv[i];

      foundMagic = findJavaMagic(fileHandle);
      processJavaFile(fileHandle);
    }

    rc = fclose(fileHandle);
    if( rc ) {
      die ("Could not close file: %s.\n", FILENAME);
    }
    CLASSNAME=0;
  } /*end parsing arguments which are filenames*/

  return 0;
}

Comment 1 Jeff Johnson 1999-06-13 03:16:59 UTC
The file javadeps.c has been added to rpm.