/* * port -- test an application for portability, using databases in the * same format as the Opcom .ref files, not the ones used for * pscore/jscore. * * Copyright (C) 2006, David Collier-Brown, davecb@datacenterworks.com * * This program is free software: It is available under the terms * of the LGPL, Versions 2 or 3, or of the (Sun) CDDL. */ #include #include #include /* For stat(). */ #include /* for PATH_MAX */ #include /* For strspn(). */ #include #include /* For isalnum(). */ #include /* For calloc(). */ #include /* For assert(). */ #include /* For stat(). */ #include #include /* Parameters */ #define SORT 1 /* #define DEBUG 1 */ /* File Globals. */ #define MAXLINE 1024 #define YES 1 #define NO 0 static char *ProgName = NULL; static char CppOpts[MAXLINE]; static FILE *OutFP; static int ErrnoProcessing = NO; static int ErrnoLevel = -1; static int Verbose = 0; static int IgnoreIndirect = YES; static int RealCount = NO; static int Fortran = NO; /* Avoid using Sun cpp unless everything is perfect: */ /* it exists after N errors, missing later issues. */ static char *Cpp = "gcc -E"; /* SUNCPP is "cc -E -xCC -erroff" */ /* GNUCPP is "gcc -E" */ static char *Database = "linux"; #define DBPATH "/opt/DCWport/databases/%s.ref" /* Issue Database */ typedef struct issue_t { char *interface; /* Interface name. */ int weight; /* AKA score */ int errno:1; /* Is errno use a prerequisite? */ /* Other flags go here. */ int count; /* How many of these have we seen? */ char *description; /* Optional comment and example text */ } ISSUE; static ISSUE *IssueDB = NULL; static int IssueElements = 0; static int port(const char *file, struct stat *statbuf); static char *gettok(char *string, const char *sep, char *nextc); static char *quotespan(char *p); static int followingParen(char *p, char ch); static int pragma(char *buffer, int *lineNo, char *file); static int parse(char *buffer, int *lineNo, char *file); static void loadDatabase(char *name); static ISSUE *inDatabase(char *token); static int compareDb(const void *p, const void *q); static void report(char *p, ISSUE *q, char *file, int lineNo, int errnoLive); static void reportScore(void); static void usage() { (void) fprintf(stderr, "You must specify at least one file.\n"); (void) fprintf(stderr, "Usage: %s [-d database][-Iinclude][-Ddefine][eEisv] file\n", ProgName); } /* * main -- process options and set up the environment before * running port(). */ void main(int argc, char *argv[]) { int i, rc = 0; struct stat statbuf; ProgName = argv[0]; CppOpts[0] = '\0'; /* Process options */ for (i=1; i < argc; i++) { if (argv[i][0] == '-') { switch (argv[i][1]) { case 'D': /* -Ddefine */ (void) strlcat(CppOpts, argv[i], MAXLINE); (void) strlcat(CppOpts, " ", MAXLINE); break; case 'd': Database = argv[i+1]; i++; break; case 'e': /* grep for errno, database lacks ERRNO tag */ ErrnoProcessing = YES; break; case 'f': /* Fortran. */ Fortran = YES; break; case 'i': /* Report indirect calls. */ IgnoreIndirect = NO; break; case 'I': /* -Idir */ (void) strlcat(CppOpts, argv[i], MAXLINE); (void) strlcat(CppOpts, " ", MAXLINE); break; case 's': RealCount = YES; break; case 'v': Verbose = 1; break; default: (void) fprintf(stderr, "%s: Unrecognized " "option \"-%c\" ignored.\n", ProgName, argv[i][1]); break; } } else { /* A file, so we stop processing options */ break; } } if (i >= argc) { /* Someone forgot to specify a file. */ usage(); exit(1); } /* Open the pipe to the output filter */ #ifdef SORT if ((OutFP = popen("sort -u -k 4,4 -k 1,1 -k 3,3n", "w")) == NULL) { (void) fprintf(stderr, "%s: can't open a pipe to sort, " "halting.\n", ProgName); exit(3); } #else OutFP = stdout; #endif /* Open and load database next. */ loadDatabase(Database); /* Iterate across named files. */ for ( ; i < argc; i++) { if (stat(argv[i], &statbuf) < 0) { (void) fprintf(stderr, "%s: can't open %s, ignored.\n", ProgName, argv[i]); continue; } if (Verbose) { (void) fprintf(stderr, "%s: porting %s\n", ProgName, argv[i]); } rc += port(argv[i], &statbuf); } #ifdef SORT (void) pclose(OutFP); #endif reportScore(); exit(rc); } /* * port -- process a file */ static int port(const char *path, struct stat *statbuf) { FILE *fp; char buffer[MAXLINE * 2]; int lineNo = 0, rc = 0; char file[PATH_MAX+1]; if ((statbuf->st_mode & S_IFDIR) == S_IFDIR) { /* Use find at the beginning of the pipeline. */ /* Select ONLY c, h and cpp files, not Fortran. */ (void) snprintf(buffer, sizeof(buffer), "find %s -type f -name '*\\.c' " "-o -name '*\\.h' -o -name '*\\.cpp' | " "egrep -v 'SCCS|RCS|\\.svn' | xargs " "%s %s ", path, Cpp, CppOpts); } else if (Fortran == YES) { /* Turn off CPP, but only for a single file. */ (void) snprintf(buffer, sizeof(buffer), "(echo '# 1 \"%s\"'; cat %s)", path, path); } else { /* Just run the c file through cpp. */ (void) snprintf(buffer, sizeof(buffer), "%s %s %s ", Cpp, CppOpts, path); } if (Verbose) { (void) fprintf(stderr,"cpp command = %s\n", buffer); } if ((fp = popen(buffer, "r")) == NULL) { (void) fprintf(stderr,"%s: can't preprocess %s using the " "command %s, halting.\n", ProgName, path, buffer); return 1; } for (; fgets(buffer, sizeof(buffer), fp) != NULL; lineNo++) { if (Verbose) { (void) fputs(buffer, stderr); } if (*buffer == '\n') { /*EMPTY*/ ; /* Just increment the line counter. */ } else if (*buffer == '#') { /* Handle a pragma or # line directive. */ rc += pragma(buffer, &lineNo, &file[0]); } else { rc += parse(buffer, &lineNo, &file[0]); } } (void) pclose(fp); return rc; } /* * pragma -- parse pragmas and #line directives */ static int pragma(char *buffer, int *lineNo, char *file) { char *p, nextc; if (buffer[1] == ' ') { /* It's a # line directive, */ /* get the lineNo and filename. */ *lineNo = atoi(gettok(buffer, "# \t\n", &nextc)) -1; if ((p = gettok(NULL, " \t\n",&nextc)) != NULL) { (void) strlcpy(file, p, PATH_MAX); } } /* Otherwise its a pragma or #ident and we ignore it. */ return 0; } /* * parse -- parse the contents of a regular line */ static int parse(char *buffer, int *lineNo, char *file) { int curlyLevel = 0; char *p, nextc; const char *skip = " \t\n~!@#$%^&*()-+=[]|:;<>,./?"; ISSUE *q; for (p=gettok(buffer, skip, &nextc); p != NULL; p = gettok(NULL, skip, &nextc)) { #ifdef DEBUG (void) fprintf(stderr, ">> \"%s\"\n", p); #endif if (isalnum(*p) || *p == '_') { /* Alphanumeric token. */ if ((q = inDatabase(p)) != NULL && followingParen(p, nextc)) { report(p, q, file, *lineNo, ErrnoLevel >= curlyLevel); } else if (strstr(p, "errno") != NULL) { /* errno is mentioned. */ ErrnoLevel = curlyLevel; } } else if (*p == '"' || *p == '\'') { /* Quoted string or character. */ /*EMPTY */ ; } else if (*p == '{') { curlyLevel++; } else if (*p == '}') { if (ErrnoLevel == curlyLevel) { /* We passed the closing curly of */ /* its declaration, turn it off. */ ErrnoLevel = -1; } curlyLevel--; } else { /* End of line (or false negative). */ break; } } return 0; } /* * report -- report matches, conditionally on direct calls, * presence of errno, etc. */ static void report(char *p, ISSUE *q, char *file, int lineNo, int errnoLive) { char *annotations = ""; /* Never report if the source is in /user/include. */ if (strncmp("/usr/include", &file[1], 10) == 0) { return; } /* Update count of interfaces to port. */ q->count++; /* Ignore (annotate) likely false positives. */ if (q->errno == 1 && errnoLive == 0) { /* If this test requires errno to be live and */ /* it isn't, mark the line "NC". */ annotations = "NC"; if (RealCount == YES) { q->count--; } } else if (IgnoreIndirect == YES && strchr(">:.", *(p-1)) != NULL) { /* Ignore indirect and method calls: */ /* this is probably not the library call we care about. */ annotations = "NC"; if (RealCount == YES) { q->count--; } } /* Report in error-message format. */ (void) fprintf(OutFP,"%s, line %d: %s %s\n", file, lineNo, p, annotations); if (Verbose) { (void) fprintf(stderr, "## %s, line %d: %s %s, " "weight=%d, errno=%d\n", file, lineNo, p, annotations, q->weight, q->errno); } } /* *reportScore -- at the very end, print a table of instances*weights * for estimating purposes. */ void reportScore(void) { int i, calls = 0, subtotal = 0, total = 0; ISSUE *p; (void) fprintf(stdout, "\n%20s %6s %6s\n", "Interface Name", "Weight", "Calls"); (void) fprintf(stdout, "%20s %6s %6s\n", "--------------","------","-----"); for (p=IssueDB, i=0; i < IssueElements; i++,p++) { if (p->count > 0) { (void)fprintf(stdout,"%20s %6d %6d\n", p->interface, p->weight, p->count); calls += p->count; subtotal += p->weight; total += (p->weight * p->count); } } (void) fprintf(stdout, "%20s %6s %6s\n", "--------------","------","-----"); (void) fprintf(stdout, "%20s %6d %6d\n", "Total", subtotal, calls); (void) fprintf(stdout, "%20s %6d\n", "Difficulty", total); } /* * loadDatabase -- load an issues file into an issue structure */ static void loadDatabase(char *name) { char file[PATH_MAX], buffer[MAXLINE], nextc; FILE *fp; ISSUE *issue; int lines = 0; (void) snprintf(file, sizeof(file), DBPATH, name); if ((fp = fopen(file, "r")) == NULL) { (void) fprintf(stderr, "%s: can't load database \"%s\", " "halting.\n", ProgName, file); exit(3); } IssueDB = calloc(10240, sizeof(struct issue_t)); assert(IssueDB != NULL); issue = IssueDB; for(; fgets(buffer, sizeof(buffer), fp) != NULL; lines++) { assert(IssueElements < 10240); /* (void) fputs(buffer, stdout); */ if (*buffer == '#') { /*EMPTY*/ ; /* Skip comments. */ } else if (*buffer == '%') { /* Copy to stdout. */ (void) printf("%s",buffer); } else if (strncmp(buffer, "NAME", 4) == 0) { issue->interface = strdup( gettok(&buffer[4],": \t\n", &nextc)); } else if (strncmp(buffer, "WEIGHT:", 7) == 0) { issue->weight = atoi(&buffer[7]); ; } else if (strncmp(buffer, "ERRNO", 5) == 0) { /* Non-probabalistic errno flag */ issue->errno = YES; } else if (strncmp(buffer, "BEGIN_COMMENT", 8) == 0 || strncmp(buffer, "END_COMMENT", 8) == 0 || strncmp(buffer, "BEGIN_EXAMPLE", 8) == 0) { /*EMPTY*/ ; /* We use these elsewhere. */ } else if (strncmp(buffer, "END_EXAMPLE", 8) == 0) { if (issue->interface == NULL) { (void) fprintf(stderr, "%s: missing interface name " "in %s, line %d.\n", ProgName, file, lines); } issue->count = 0; #ifdef DEBUG if (Verbose) { (void) fprintf(stderr, "interface=%s, " "weight=%d, errno=%d, issue=%d\n", issue->interface, issue->weight, issue->errno, IssueElements); } #endif /* DEBUG */ issue++; IssueElements++; } else { /* Conditionally check to see if errno is mentioned. */ /* Not needed if database has ERRNO tags in it. */ if (ErrnoProcessing == YES && (strstr(buffer, "errno") != NULL || strstr(buffer, "error condition") != NULL)) { issue->errno = 1; } } } (void) fclose(fp); (void) qsort(IssueDB, IssueElements, sizeof(struct issue_t), compareDb); } /* * compareDb -- sorting comparison function */ static int compareDb(const void *p, const void *q) { return strcmp(((ISSUE *)p)->interface,((ISSUE *)q)->interface); } /* * seachDb -- bsearch comparison function */ static int searchDb(const void *key, const void *issue) { return strcmp((char *)key,((ISSUE *)issue)->interface); } /* * inDatabase -- see if a token is an interface-name in the database. */ static ISSUE * inDatabase(char *key) { ISSUE *p; if ((p = (ISSUE *)bsearch(key, IssueDB, IssueElements, sizeof(struct issue_t), searchDb)) == NULL) { return NULL; } else { return (strcmp(p->interface, key) == 0)? p: NULL; } } /* * gettok -- get a token from a line, much like strtok, except make * the next character visible in a variable for lookahead and * handle quoted strings. */ static char * gettok(char *string, const char *skip, char *nextc) { static char *p, *q; if (string != NULL ) { /* Initialize everything */ p = q = string; *nextc = '\0'; } else { /* put nextc back. */ *q = *nextc; /* Set p to beginning of next token. */ p = q + 1; } if (*q == '\0' || *p == '\0') { /* We hit the end of the line. */ return NULL; } /* Skip over the uninteresting stuff */ p += strspn(p, skip); /* If we found a string, take it as the token. */ if (*p == '"' || *p == '\'') { q = quotespan(p); } else { q = p + strcspn(p, skip); } *nextc = *q; *q = '\0'; return p; } /* .* quotespan -- advance pointer over a string, ignoring * any escaped characters. Return a pointer, not a length. */ static char * quotespan(char *p) { char ch = *p; for (p++; *p && *p != ch; p++) { if (*p == '\\') { /* Skip this and next char */ p++; } } return ++p; } /* * followingParen -- see if the next non-whitespace is a '('. * Used in concert with gttok(). */ static int followingParen(char *p, char ch) { if (ch == '(') { return YES; } /* Skip to end of string, then ober the null */ while (*p) p++; p++; /* Skip whitespace */ for (; *p != '\0'; p++) { if ( !isspace(*p)) { break; } } return (*p == '('); }