blob: a84be627939abba0450ca2b231b3b2010712086f [file] [log] [blame]
/**************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
/* All Java Virtual Machine Specs are from
* "The Java Virtual Machine Specification", T. Lindholm, F. Yellin
* (JVMS)
*/
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <limits.h>
#if defined(UNX) || defined(OS2)
#include <unistd.h>
#include <netinet/in.h> /* ntohl(), ntohs() */
#elif defined(WNT)
#include <io.h>
#define access _access
#define vsnprintf _vsnprintf
#define CDECL _cdecl
#define F_OK 00
#define PATH_MAX _MAX_PATH
#define ntohl(x) ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \
(((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24))
#define ntohs(x) ((((x) >> 8) & 0xff) | (((x) & 0xff) << 8))
#endif
#if defined(OS2)
#define CDECL
#endif
/* max. length of line in response file */
#define RES_FILE_BUF 65536
struct file {
char *pname;
FILE *pfs;
};
struct growable {
int ncur;
int nmax;
char **parray;
};
typedef struct file file_t;
typedef unsigned char uint8;
typedef unsigned short uint16;
typedef unsigned int uint32;
struct utf8 {
uint16 nlen;
void *pdata;
};
typedef struct utf8 utf8_t;
/* The contents of the Constant_pool is described in JVMS p. 93
*/
enum {
CONSTANT_Class = 7,
CONSTANT_Fieldref = 9,
CONSTANT_Methodref = 10,
CONSTANT_InterfaceMethodref = 11,
CONSTANT_String = 8,
CONSTANT_Integer = 3,
CONSTANT_Float = 4,
CONSTANT_Long = 5,
CONSTANT_Double = 6,
CONSTANT_NameAndType = 12,
CONSTANT_Utf8 = 1
};
enum { NGROW_INIT = 10, NGROW = 2 };
static char *pprogname = "javadep";
static char csep = ';';
#if defined (UNX) || defined(OS2)
#define CDECL
static char cpathsep = '/';
#elif defined (WNT) || defined(OS2)
static char cpathsep = '\\';
#endif
static FILE *pfsout = NULL;
static char *pout_file = NULL;
/* prototypes */
uint8 read_uint8(const file_t *pfile);
uint16 read_uint16(const file_t *pfile);
uint32 read_uint32(const file_t *pfile);
void skip_bytes(const file_t *pfile, const size_t nnum);
char *escape_slash(const char *pstr);
int is_inner(const char *pstr);
void print_dependencies(const struct growable *pdep,
const char* pclass_file);
void process_class_file(const char *pfilenamem,
const struct growable *pfilt);
char *utf8tolatin1(const utf8_t a_utf8);
void *xmalloc(size_t size);
void *xcalloc(size_t nmemb, size_t size);
void *xrealloc(void *ptr, size_t size);
void grow_if_needed (struct growable *pgrow);
int append_to_growable(struct growable *, char *);
struct growable *allocate_growable(void);
void free_growable(struct growable *pgrowvoid);
void create_filters(struct growable *pfilt, const struct growable *pinc);
void usage(void);
void err_quit(const char *, ...);
void silent_quit(void);
#ifdef WNT
/* poor man's getopt() */
int simple_getopt(char *pargv[], const char *poptstring);
char *optarg = NULL;
int optind = 1;
int optopt = 0;
int opterr = 0;
#endif
uint8
read_uint8(const file_t *pfile)
{
/* read a byte from classfile */
int nread;
uint8 ndata;
nread = fread(&ndata, sizeof(uint8), 1, pfile->pfs);
if ( !nread ) {
fclose(pfile->pfs);
err_quit("%s: truncated class file", pfile->pname);
}
return ndata;
}
uint16
read_uint16(const file_t *pfile)
{
/* read a short from classfile and convert it to host format */
int nread;
uint16 ndata;
nread = fread(&ndata, sizeof(uint16), 1, pfile->pfs);
if ( !nread ) {
fclose(pfile->pfs);
err_quit("%s: truncated class file", pfile->pname);
}
ndata = ntohs(ndata);
return ndata;
}
uint32
read_uint32(const file_t *pfile)
{
/* read an int from classfile and convert it to host format */
int nread;
uint32 ndata;
nread = fread(&ndata, sizeof(uint32), 1, pfile->pfs);
if ( !nread ) {
fclose(pfile->pfs);
err_quit("%s: truncated class file", pfile->pname);
}
ndata = ntohl(ndata);
return ndata;
}
utf8_t
read_utf8(const file_t *pfile)
{
/* Read a java utf-8-string with uint16 length prependend
* from class file. Returns utf8 struct
* with fresh allocated datablock,
* caller is responsible for freeing.
* Data is still in network byteorder
*/
utf8_t a_utf8;
int nread;
a_utf8.pdata = NULL;
a_utf8.nlen = read_uint16(pfile);
if (a_utf8.nlen > 0) {
a_utf8.pdata = xmalloc(a_utf8.nlen*sizeof(char));
nread = fread(a_utf8.pdata, a_utf8.nlen*sizeof(char), 1, pfile->pfs);
if ( !nread ) {
fclose(pfile->pfs);
err_quit("%s: truncated class file", pfile->pname);
}
}
return a_utf8;
}
char *utf8tolatin1(const utf8_t a_utf8)
{
/* function returns fresh allocated zero terminated string,
* caller is responsible for freeing
*/
/* JVMS p. 101: the null byte is encoded using a two byte format,
* Java Virtual Machine Utf8 strings differ in this respect from
* standard UTF-8 strings
*/
/* Multibyte data is in network byte order */
char *p;
char *pp;
char *pstr;
pstr = pp = xmalloc((a_utf8.nlen+1) * sizeof(char));
for ( p = (char*)a_utf8.pdata;
p < (char*)a_utf8.pdata+a_utf8.nlen;
p++ ) {
if ( *p & 0x80 ) {
err_quit("sorry, real UTF8 decoding not yet implemented\n");
} else {
*pp++ = *p;
}
}
*pp = '\0';
return pstr;
}
void
skip_bytes(const file_t *pfile, const size_t nnumber)
{
/* skip a nnumber of bytes in classfile */
if ( fseek(pfile->pfs, nnumber, SEEK_CUR) == -1 )
err_quit("%s: %s", pfile->pname, strerror(errno));
}
void
add_to_dependencies(struct growable *pdep,
const struct growable *pfilt,
char *pdepstr,
const char *pclass_file)
{
/* create dependencies */
int i;
int nlen_filt, nlen_str, nlen_pdepstr;
char *pstr, *ptrunc;
char path[PATH_MAX+1];
char cnp_class_file[PATH_MAX+1];
char cnp_str[PATH_MAX+1];
nlen_pdepstr = strlen(pdepstr);
pstr = xmalloc((nlen_pdepstr+6+1)*sizeof(char));
memcpy(pstr, pdepstr, nlen_pdepstr+1);
strncat(pstr, ".class", 6);
if ( pfilt->ncur == 0 ) { /* no filters */
if ( access(pstr, F_OK) == 0 ) {
append_to_growable(pdep, strdup(pstr));
}
} else {
nlen_str = strlen(pstr);
for ( i = 0; i < pfilt->ncur; i++ ) {
nlen_filt = strlen(pfilt->parray[i]);
if ( nlen_filt + 1 + nlen_str > PATH_MAX )
err_quit("path to long");
memcpy(path, pfilt->parray[i], nlen_filt);
path[nlen_filt] = '/';
memcpy( path+nlen_filt+1, pstr, nlen_str+1);
if ( access(path, F_OK) != 0 ) {
free(pstr);
pstr = NULL;
return; /* path doesn't represent a real file, don't bother */
}
/* get the canonical path */
#if defined (UNX) || defined(OS2)
if ( !(realpath(pclass_file, cnp_class_file)
&& realpath(path, cnp_str) ) ) {
err_quit("can't get the canonical path");
}
#else
if ( !(_fullpath(cnp_class_file, pclass_file, sizeof(cnp_class_file))
&& _fullpath(cnp_str, path, sizeof(cnp_str)) ) ) {
err_quit("can't get the canonical path");
}
#endif
/* truncate so that only the package prefix remains */
ptrunc = strrchr(cnp_str, cpathsep);
*ptrunc = '\0';
ptrunc = strrchr(cnp_class_file, cpathsep);
*ptrunc = '\0';
if ( !strcmp(cnp_str, cnp_class_file) ) {
free(pstr);
pstr = NULL;
return; /* identical, don't bother with this one */
}
append_to_growable(pdep, strdup(path));
}
}
free(pstr);
return;
}
char *
escape_slash(const char *pstr)
{
/* returns a fresh allocated string with all cpathsep escaped exchanged
* with "$/"
*
* caller is responsible for freeing
*/
const char *pp = pstr;
char *p, *pnp;
char *pnew_str;
int nlen_pnp, nlen_pp;
int i = 0;
while ( (p=strchr(pp, cpathsep)) != NULL ) {
++i;
pp = ++p;
}
nlen_pnp = strlen(pstr) + i;
pnp = pnew_str = xmalloc((nlen_pnp+1) * sizeof(char));
pp = pstr;
if ( i > 0 ) {
while ( (p=strchr(pp, cpathsep)) != NULL ) {
memcpy(pnp, pp, p-pp);
pnp += p-pp;
*pnp++ = '$';
*pnp++ = '/';
pp = ++p;
}
}
nlen_pp = strlen(pp);
memcpy(pnp, pp, nlen_pp+1);
return pnew_str;
}
void
print_dependencies(const struct growable *pdep, const char* pclass_file)
{
char *pstr;
int i;
pstr = escape_slash(pclass_file);
fprintf(pfsout, "%s:", pstr);
free(pstr);
for( i=0; i<pdep->ncur; ++i) {
fprintf(pfsout, " \\\n");
pstr=escape_slash(pdep->parray[i]);
fprintf(pfsout, "\t%s", pstr);
free(pstr);
}
fprintf(pfsout,"\n\n");
return;
}
int
is_inner(const char *pstr)
{
/* return true if character '$' is found in classname */
/*
* note that a '$' in a classname is not an exact indicator
* for an inner class. Java identifier may legally contain
* this chararcter, and so may classnames. In the context
* of javadep this doesn't matter since the makefile system
* can't cope with classfiles with '$'s in the filename
* anyway.
*
*/
if ( strchr(pstr, '$') != NULL )
return 1;
return 0;
}
void
process_class_file(const char *pfilename, const struct growable *pfilt)
{
/* read class file and extract object information
* java class files are in bigendian data format
* (JVMS, p. 83)
*/
int i;
uint32 nmagic;
uint16 nminor, nmajor;
uint16 ncnt;
uint16 nclass_cnt;
utf8_t* pc_pool;
uint16* pc_class;
file_t file;
struct growable *pdepen;
file.pname = (char*)pfilename;
file.pfs = fopen(file.pname,"rb");
if ( !file.pfs )
silent_quit();
nmagic = read_uint32(&file);
if ( nmagic != 0xCAFEBABE ) {
fclose(file.pfs);
err_quit("%s: invalid magic", file.pname);
}
nminor = read_uint16(&file);
nmajor = read_uint16(&file);
/* get number of entries in constant pool */
ncnt = read_uint16(&file);
#ifdef DEBUG
printf("Magic: %p\n", (void*)nmagic);
printf("Major %d, Minor %d\n", nmajor, nminor);
printf("Const_pool_count %d\n", ncnt);
#endif
/* There can be ncount entries in the constant_pool table
* so at most ncount-1 of them can be of type CONSTANT_Class
* (at leat one CONSTANT_Utf8 entry must exist).
* Usually way less CONSTANT_Class entries exists, of course
*/
pc_pool = xcalloc(ncnt,sizeof(utf8_t));
pc_class = xmalloc((ncnt-1)*sizeof(uint16));
/* pc_pool[0] is reserved to the java virtual machine and does
* not exist in the class file
*/
nclass_cnt = 0;
for (i = 1; i < ncnt; i++) {
uint8 ntag;
uint16 nindex;
utf8_t a_utf8;
ntag = read_uint8(&file);
/* we are only interested in CONSTANT_Class entries and
* Utf8 string entries, because they might belong to
* CONSTANT_Class entries
*/
switch(ntag) {
case CONSTANT_Class:
nindex = read_uint16(&file);
pc_class[nclass_cnt++] = nindex;
break;
case CONSTANT_Fieldref:
case CONSTANT_Methodref:
case CONSTANT_InterfaceMethodref:
skip_bytes(&file, 4L);
break;
case CONSTANT_String:
skip_bytes(&file, 2L);
break;
case CONSTANT_Integer:
case CONSTANT_Float:
skip_bytes(&file, 4L);
break;
case CONSTANT_Long:
case CONSTANT_Double:
skip_bytes(&file, 8L);
/* Long and Doubles take 2(!)
* entries in constant_pool_table
*/
i++;
break;
case CONSTANT_NameAndType:
skip_bytes(&file, 4L);
break;
case CONSTANT_Utf8:
a_utf8 = read_utf8(&file);
pc_pool[i] = a_utf8;
break;
default:
/* Unknown Constant_pool entry, this means we are
* in trouble
*/
err_quit("corrupted class file\n");
break;
}
}
fclose(file.pfs);
pdepen = allocate_growable();
for (i = 0; i < nclass_cnt; i++) {
char *pstr, *ptmpstr;
pstr = ptmpstr = utf8tolatin1(pc_pool[pc_class[i]]);
/* we are not interested in inner classes */
if ( is_inner(pstr) ) {
free(pstr);
pstr = NULL;
continue;
}
/* strip off evt. array indicators */
if ( *ptmpstr == '[' ) {
while ( *ptmpstr == '[' )
ptmpstr++;
/* we only interested in obj. arrays, which are marked with 'L' */
if ( *ptmpstr == 'L' ) {
char *p = pstr;
pstr = strdup(++ptmpstr);
/* remove final ';' from object array name */
pstr[strlen(pstr)-1] = '\0';
free(p);
} else {
free(pstr);
pstr = NULL;
}
}
if (pstr) {
add_to_dependencies(pdepen, pfilt, pstr, file.pname);
free(pstr);
}
}
print_dependencies(pdepen, file.pname);
free_growable(pdepen);
pdepen = NULL;
for (i = 0; i < ncnt; i++)
free(pc_pool[i].pdata);
free(pc_class);
free(pc_pool);
}
void *
xmalloc(size_t size)
{
void *ptr;
ptr = malloc(size);
if ( !ptr )
err_quit("out of memory");
return ptr;
}
void *
xcalloc(size_t nmemb, size_t size)
{
void *ptr;
ptr = calloc(nmemb, size);
if ( !ptr )
err_quit("out of memory");
return ptr;
}
void *
xrealloc(void *ptr, size_t size)
{
ptr = realloc(ptr, size);
if ( !ptr )
err_quit("out of memory");
return ptr;
}
void
err_quit(const char* fmt, ...)
{
/* No dependency file must be generated for any error condition,
* just print message and exit.
*/
va_list args;
char buffer[PATH_MAX];
va_start(args, fmt);
if ( pprogname )
fprintf(stderr, "%s: ", pprogname);
vsnprintf(buffer, sizeof(buffer), fmt, args);
fputs(buffer, stderr);
fputc('\n', stderr);
va_end(args);
/* clean up */
if ( pfsout && pfsout != stdout ) {
fclose(pfsout);
unlink(pout_file);
}
exit(1);
}
void
silent_quit()
{
/* In some cases we should just do a silent exit */
/* clean up */
if ( pfsout && pfsout != stdout ) {
fclose(pfsout);
unlink(pout_file);
}
exit(0);
}
int append_to_growable(struct growable *pgrow, char *pstr)
{
/* append an element pstr to pgrow,
* return new number of elements
*/
grow_if_needed(pgrow);
pgrow->parray[pgrow->ncur++] = pstr;
return pgrow->ncur;
}
void
grow_if_needed(struct growable *pgrow)
{
/* grow growable arrays */
if ( pgrow->ncur >= pgrow->nmax ) {
pgrow->parray = xrealloc(pgrow->parray,
(NGROW*pgrow->nmax)*sizeof(char*));
pgrow->nmax *= NGROW;
}
return;
}
struct growable *allocate_growable(void)
{
/* allocate an growable array,
* initialize with NGROW_INIT elements
*/
struct growable *pgrow;
pgrow = xmalloc(sizeof(struct growable));
pgrow->parray = xmalloc(NGROW_INIT*sizeof(char *));
pgrow->nmax = NGROW_INIT;
pgrow->ncur = 0;
return pgrow;
}
void
free_growable(struct growable *pgrow)
{
int i;
for( i = 0; i < pgrow->ncur; i++ )
free(pgrow->parray[i]);
free(pgrow->parray);
free(pgrow);
}
void
create_filters(struct growable *pfilt, const struct growable *pinc)
{
char *p, *pp, *pstr;
int i, nlen, nlen_pstr;
/* break up includes into filter list */
for ( i = 0; i < pinc->ncur; i++ ) {
pp = pinc->parray[i];
while ( (p = strchr(pp, csep)) != NULL) {
nlen = p - pp;
pstr = xmalloc((nlen+1)*sizeof(char*));
memcpy(pstr, pp, nlen);
pstr[nlen] = '\0';
append_to_growable(pfilt, pstr);
pp = p + 1;
}
nlen_pstr = strlen(pp);
pstr = xmalloc((nlen_pstr+1)*sizeof(char*));
memcpy(pstr, pp, nlen_pstr+1);
append_to_growable(pfilt, pstr);
}
}
void
usage()
{
fprintf(stderr,
"usage: %s [-i|-I includepath ... -s|-S seperator "
"-o|-O outpath -v|-V -h|-H] <file> ....\n",
pprogname);
}
#ifdef WNT
/* my very simple minded implementation of getopt()
* it's to sad that getopt() is not available everywhere
* note: this is not a full POSIX conforming getopt()
*/
int simple_getopt(char *pargv[], const char *poptstring)
{
char *parg = pargv[optind];
/* skip all response file arguments */
if ( parg ) {
while ( *parg == '@' )
parg = pargv[++optind];
if ( parg[0] == '-' && parg[1] != '\0' ) {
char *popt;
int c = parg[1];
if ( (popt = strchr(poptstring, c)) == NULL ) {
optopt = c;
if ( opterr )
fprintf(stderr, "Unknown option character `\\x%x'.\n", optopt);
return '?';
}
if ( *(++popt) == ':') {
if ( parg[2] != '\0' ) {
optarg = ++parg;
} else {
optarg = pargv[++optind];
}
} else {
optarg = NULL;
}
++optind;
return c;
}
}
return -1;
}
#endif
int CDECL
main(int argc, char *argv[])
{
int bv_flag = 0;
struct growable *presp, *pincs, *pfilters;
int c, i, nall_argc;
char **pall_argv;
presp = allocate_growable();
/* FIXME: cleanup the option parsing */
/* search for response file, read it */
for ( i = 1; i < argc; i++ ) {
char *parg = argv[i];
char buffer[RES_FILE_BUF];
if ( *parg == '@' ) {
FILE *pfile = fopen(++parg, "r");
if ( !pfile )
err_quit("%s: %s", parg, strerror(errno));
while ( !feof(pfile) ) {
char *p, *token;
if ( fgets(buffer, RES_FILE_BUF, pfile) ) {;
p = buffer;
while ( (token = strtok(p, " \t\n")) != NULL ) {
p = NULL;
append_to_growable(presp, strdup(token));
}
}
}
fclose(pfile);
}
}
/* copy all arguments incl. response file in one array
* for parsing with getopt
*/
nall_argc = argc + presp->ncur;
pall_argv = xmalloc((nall_argc+1)*sizeof(char *));
memcpy(pall_argv, argv, argc*sizeof(char *));
memcpy(pall_argv+argc, presp->parray, presp->ncur*sizeof(char *));
*(pall_argv+argc+presp->ncur) = '\0'; /* terminate */
opterr = 0;
pincs = allocate_growable();
#ifdef WNT
while( (c = simple_getopt(pall_argv, ":i:I:s:S:o:OhHvV")) != -1 ) {
#else
while( (c = getopt(nall_argc, pall_argv, ":i:I:s:S:o:OhHvV")) != -1 ) {
#endif
switch(c) {
case 'i':
case 'I':
append_to_growable(pincs, strdup(optarg));
break;
case 's':
case 'S':
csep = optarg[0];
break;
case 'o':
case 'O':
pout_file = optarg;
break;
case 'h':
case 'H':
usage();
return 0;
break;
case 'v':
case 'V':
bv_flag = 1;
break;
case '?':
if (isprint (optopt))
fprintf (stderr,
"Unknown option `-%c'.\n", optopt);
else
fprintf (stderr,
"Unknown option character `\\x%x'.\n",
optopt);
usage();
return 1;
break;
case ':':
fprintf(stderr, "Missing parameter.\n");
usage();
return 1;
break;
default:
usage();
return 1;
break;
}
}
pfilters = allocate_growable();
create_filters(pfilters, pincs);
free_growable(pincs);
pincs = NULL;
if ( pout_file ) {
pfsout = fopen(pout_file, "w");
if ( !pfsout )
err_quit("%s: %s", pout_file, strerror(errno));
} else {
pfsout = stdout;
}
/* the remaining arguments are either class file
* names or response files, ignore response file
* since they have already been included
*/
for ( i = optind; i < nall_argc; i++ ) {
char *parg = pall_argv[i];
if ( *parg != '@' ) {
process_class_file(parg, pfilters);
if ( pfsout != stdout ) {
if ( bv_flag )
printf("Processed %s ...\n", parg);
}
}
}
free_growable(pfilters);
pfilters = NULL;
free(pall_argv);
pall_argv = NULL;
free_growable(presp);
presp = NULL;
fclose(pfsout);
exit(0);
}