abi_audit.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include "abi_audit.h"
#include <time.h>
/* to be used by sequence_t data structure */
#define PUBLIC 1
#define PRIVATE 2
#define UNEXPORTED 3
/* Global variables */
char *program;
int Debug = 0; /* Debug initialized to zero */
int Total_relcnt = 0; /* Total_relcnt is initialized to 0 */
int iflag;
/* local variables */
static int Num_abi_dirs = 0; /* # of abi_dirs read in */
static int Tflag;
static int gflag;
static int lflag;
static int nflag;
static int oflag;
static int pflag;
static int sflag;
static int tflag;
/* tmp abi_audit output files */
/* Internal functions */
static int add_release(char *, int);
static int analyze_args(int, char **);
static int last_transition(liblist_t *);
static int load_liblog(void);
static int proc_intf_check_dir(char *);
static int process_abi_dirs(void);
static int process_db_options(void);
static int read_intf_check_file(char *, int);
static int skip_line(char *);
liblist_t *);
static void add_sccs_keyword_and_release(FILE *);
static void cleanup(void);
static void decode_filename(char *);
static void libc_migration(liblist_t **);
static void perform_dis_check_list(liblist_t *);
static void perform_id_check(tree_t *, int);
static void perform_int_check_list(liblist_t *);
static void print_usage(char *);
static void process_unexported(tree_t *, int);
static void process_unexported_list(liblist_t *, int);
static void report_errors(tree_t *);
int
{
int i = 0;
/* process arguments and set up tmp files */
(process_db_options() == FAIL) ||
(process_abi_dirs() == FAIL)) {
cleanup();
return (1);
}
/*
* After loading ABI information from ABI_*.db and the abi_dir(s),
* identify all symbols which have disappeared in later releases
*/
while (i < Total_relcnt)
process_unexported(Sym_List, ++ i);
/* in analyze args, we've ensured that Total_relcnt >= 2 */
if (Total_relcnt < 2) {
"%s: Need to compare between 2 or more releases\n",
program);
cleanup();
return (1);
}
if (!gflag) {
/* perform discrepancy or integrity checking */
/* perform version checking */
/* print errors to stdout */
/* print out removed libs */
if (oflag) {
while (loc) {
"WARNING: %s: library is not found\n",
}
}
} else {
/* print symbol info to file */
}
return (0);
}
/*
* Print Usage information
*/
static void
print_usage(char *prog)
{
"[-g ABI_DB_filename] \n\t[-l | -n ABI_DB_filename] "
"abi_dir, ...\n\n", prog);
}
/*
* Analyze the command line arguments
*/
static int
{
int option;
int errflag = 0;
int count = 0;
switch (option) {
case 'f': /* output file to append to */
"failed to open <%s>: %s\n",
return (FAIL);
}
break;
case 'o': /* check for omissions */
oflag ++;
break;
case 'i': /* perform integrity check */
iflag ++;
break;
case 't': /* check for private->public transition */
tflag ++;
break;
case 'T': /* check for private->unexported transition */
Tflag ++;
break;
case 's': /* silent the WARNINGs */
sflag ++;
break;
case 'g': /* to generate ABI database */
"%s: fopen failed to open <%s>: %s\n",
return (FAIL);
}
gflag ++;
break;
case 'l': /* provide old intf_check output */
lflag ++;
break;
case 'p': /* report new public interfaces */
pflag ++;
break;
case 'n': /* to load user's own ABI database */
nflag ++;
break;
case 'h':
errflag ++;
break;
case '?':
errflag ++;
break;
default:
errflag ++;
break;
} /* end switch */
} /* end while */
"%s: the -n and -l options are mutually exclusive\n",
program);
errflag ++;
}
/* check that at least 2 abi_dirs are listed with the -p option on */
"%s: -l requires at least two abi_dirs to be listed\n",
program);
errflag ++;
}
/* -s option overrides -p option */
pflag = 0;
}
return (FAIL);
}
/*
* Record number of abi_dirs specified on the command line.
*/
/* Collect all the abi_dirs listed on the command line */
"%s: analyze_args: calloc: new_Abi_Dir: %s\n",
return (FAIL);
}
/* make sure Abi_Dir->lt_name is a directory */
"Please Try Again.\n",
return (FAIL);
}
/* Attach new directory to end of linked list */
} else {
}
}
return (SUCCEED);
}
/*
* Filters through -l flag to determine Num_abi_dirs
* and whether to load an Abi reference database.
*/
static int
process_db_options(void)
{
if (lflag) {
/* do not load ABI_*.db */
} else {
if (load_liblog() == FAIL)
return (FAIL);
"%s: fopen failed to open <%s> : %s\n",
return (FAIL);
}
return (FAIL);
}
}
return (SUCCEED);
}
/*
* Add the release name info to the linked list of releases. If release
* is already found in the Rel of rellist_t type, then return FAIL.
*/
static int
{
int i;
/* check to make sure we have enough rellist_t allocated */
return (FAIL);
}
for (i = 0; i < count; i ++) {
/* found an empty slot for the release name */
if (get_rel_name(i) == NULL) {
assign_rel_name(release, i);
break;
}
/* this release is a dup entry in the Rel[] array */
"%s: Error: Loading duplicate abi_dir %s\n",
return (FAIL);
}
}
return (i);
}
/*
* Routine to load the Solaris FCS ABI (Reference_Db)
*/
static int
{
char line[MAXPATHLEN];
char *releases;
int length = 0;
int rel_num, i;
int Num_db_releases;
char db_sym_name[MAXPATHLEN];
char db_lib_name[MAXPATHLEN];
char db_type[32];
char db_size[32];
char db_lib_version[RELMAX];
char db_sym_version[RELMAX];
char *db_release;
char *db_public;
char *db_private;
char *db_unexported;
char *db_scoped;
char *db_evolving;
char *db_obsolete;
char *db_unclassified;
Num_db_releases = 0;
/* process releases in database file */
line[0] = '\0';
continue;
else
break;
}
length ++;
}
length ++;
"%s: load_db: calloc: releases: %s\n",
return (FAIL);
}
const char key[] = "#Releases:";
continue;
do {
/* adding a release */
Num_db_releases ++;
Num_db_releases) == FAIL) {
return (FAIL);
}
break;
}
}
/*
* Now, the total # of release = # of releases in database file plus
* the # of abi_dirs read in at the command line
*/
/* An assertion: the ref database contains a valid "#Releases:" line */
if (Total_relcnt < 2) {
"%s: Need to compare between 2 or more releases\n",
program);
return (FAIL);
}
/* allocates necessary spaces for the following variables */
"%s: load_db: calloc: db_*: %s\n",
return (FAIL);
}
"%s: load_db: calloc: tree_node: %s\n",
return (FAIL);
}
/* get symbol name, library name, data type and data size */
db_sym_name[0] = '\0';
db_lib_name[0] = '\0';
db_size[0] = '\0';
db_type[0] = '\0';
return (FAIL);
}
/*
* Load versioning info for releases specified in the
* Reference_Db
*/
return (FAIL);
}
/* get library version and symbol version names */
for (i = 0; i < Num_db_releases; i ++) {
db_lib_version[0] = '\0';
db_sym_version[0] = '\0';
db_sym_version) != 2) {
"%s: load_db: fscanf(2) : %s\n",
return (FAIL);
}
else
else
}
/* Get category info */
return (FAIL);
}
/* Initialize info for release specified on the command line */
/* At this point, Total_relcnt=Num_db_releases+Num_abi_dirs */
for (i = Num_db_releases; i < Total_relcnt; i ++) {
/* initialize versioning info */
/* initialize categories */
}
/* symbol assignments */
if (!sym->st_sym_name) {
"%s: load_db: strdup: sym->st_sym_name: %s\n",
return (FAIL);
}
/* library assignments */
if (!lib->lt_lib_name) {
"%s: load_db: strdup: lib->lt_lib_name: %s\n",
return (FAIL);
}
/* category assignments */
cat->ct_unexported =
if (oflag) {
/*
* if checks for omission, make sure this library
* still exists under current test directory. If
* yes, checks for any symbol disappears illegally.
* If not, make sure the symbol isn't unexported
* in previous release. In that case, add the library
* onto the removed library linked list.
*/
} else {
lib->lt_lib_name);
}
}
} else {
/* We need to check throughout the list of libs */
}
/* build tree node and insert symbol info into Sym_List */
return (FAIL);
}
return (SUCCEED);
}
/*
* Process the intf_check output for each of the abi_dirs listed on the
* command line
*/
static int
process_abi_dirs(void)
{
int num_nodes;
int i, j;
/*
* create Rel linked list and assign the proper release bitmask
* to reference each release.
*/
if (!Rel) {
}
for (i = 0; i < (num_nodes - 1); i ++) {
for (j = 0; j < RELMAX; j ++) {
<< (RELMAX - 1 - j);
}
}
}
for (j = 0; j < (Total_relcnt % RELMAX); j ++) {
<< (RELMAX - 1 - j);
}
/* Only process the abi_dirs passed in from the command line */
return (FAIL);
}
}
return (SUCCEED);
}
/*
* Perform Discrepancy or Integrity Check on the AVL tree based on flag setting
*/
static void
{
if (rootptr) {
if (flag)
else
}
}
/*
* Process intf_check Directory
*/
static int
{
char path[MAXPATHLEN];
char data_file[MAXPATHLEN];
char release[MAXPATHLEN];
int total_path_length;
int rel_flag = 0;
int dir_len;
const char *audit = "audit/";
int rel_index;
/* 5 ("audit") + 1 ("/") + 1 ("\0") + optionally 1 ("/") */
}
if (total_path_length >= MAXPATHLEN) {
return (FAIL);
}
/* Ensure intf_check_dir ends with a "/" char. */
return (FAIL);
}
}
/* look for release string file */
"%s: %s: %s: No such directory. Please try again.\n",
return (FAIL);
}
/* Ensure the file specifying the release ends with ".rel" */
if (rel_flag != 0) {
"release file found under %s: %s, %s.\n",
break;
}
>= MAXPATHLEN) {
"%s: proc_intf_check_dir: "
"strlcpy: %s\n",
break;
}
/* null terminate release string to parse off ".rel" */
/* command line processing of releases */
if (release[0] == '\0') {
"%s: Error: No release is specified with "
".rel file\n", program);
break;
}
rel_flag ++;
}
}
return (FAIL);
}
/*
* if no release file is found, then use the basename of intf_check
* dir as the release name
*/
if (rel_flag != 1) {
MAXPATHLEN) {
"%s: proc_intf_check_dir: strlcpy: %s\n",
return (FAIL);
}
}
return (FAIL);
}
/* check for existence of audit/ directory in intf_check_dir */
"%s: %s: %s: No such directory. Please try again.\n",
return (FAIL);
}
/* Ready to process intf_check files located in the audit dir */
>= MAXPATHLEN) {
"%s: proc_intf_check_dir: "
"strlcat: %s: %s\n",
break;
}
== FAIL) {
break;
}
}
}
return (errmsg);
}
/*
* Read and Process intf_check files
*/
static int
{
char line[MAXPATHLEN];
char *filename;
char *symbolname;
char *symbolversion;
return (FAIL);
}
line[0] = '\0';
/*
* "line" will match one of the following patterns:
* <symbol version>: <symbol name>
* <symbol version>: <symbol name> <data size>
*/
break;
continue;
/*
* If there is insufficient memory, the structs are not
* freed since this will result in an immediate fatal error
* upon returning FAIL to proc_intf_check_dir().
*/
"%s: read_intf_check_file: calloc: %s\n",
return (FAIL);
}
/* Get the version info. */
"%s: read_intf_check_file: build_cat_bits: %s\n",
return (FAIL);
}
if (lflag) {
return (FAIL);
}
"%s: read_intf_check_file: build_lib_tag: %s\n",
return (FAIL);
}
if (!oflag)
/* Get the symbol name */
/* Adds symbol to Sym_List */
return (FAIL);
}
return (SUCCEED);
}
/*
* Returns the release where a classification transition is detected
* e.g., public->private
*/
static int
{
int release = 0;
int count = 0;
bitmask = get_rel_bitmask(0);
while (count < Total_relcnt) {
count ++;
}
return (release);
}
/*
* Walk through the unique linked list, and check if symbol is migrated into
* libc.so.1. lt_libc_migrate will initialized to FALSE first.
*/
static void
{
liblist_t *q;
while (p) {
if (((p->lt_scenario == SCENARIO_04) ||
(p->lt_scenario == SCENARIO_08) ||
(p->lt_scenario == SCENARIO_13) ||
(p->lt_scenario == SCENARIO_14)) &&
q = *lib;
p->lt_libc_migrate = FALSE;
while (q) {
if ((strstr(q->lt_lib_name,
((q->lt_scenario == SCENARIO_01) ||
(q->lt_scenario == SCENARIO_02) ||
(q->lt_scenario == SCENARIO_07) ||
(q->lt_scenario == SCENARIO_05))) {
p->lt_libc_migrate = TRUE;
break;
}
q = q->lt_next;
}
}
p = p->lt_next;
}
}
/*
* Perform Discrepancy Checking on the linked list
*/
void
{
while (p) {
/*
* #1, a new public symbol
* is introduced
*/
p->lt_scenario = SCENARIO_01;
}
/*
* #2, a public symbol exists
* at all releases
*/
p->lt_scenario = SCENARIO_02;
}
/*
* #3, a old public symbol
* becomes private
*/
p->lt_scenario = SCENARIO_03;
}
/*
* #4, public symbol is unexported
*/
p->lt_scenario = SCENARIO_04;
}
/*
* #5, a new private symbol is introduced
*/
p->lt_scenario = SCENARIO_05;
}
/*
* #6, a private symbol becomes public
*/
p->lt_scenario = SCENARIO_06;
}
/*
* #7, a private symbol exists at all releases
*/
p->lt_scenario = SCENARIO_07;
}
/*
* #8, a private symbol is unexported
*/
p->lt_scenario = SCENARIO_08;
}
/*
* #9, previously unexported symbol
* comes back as public
*/
p->lt_scenario = SCENARIO_09;
}
/*
* #10, previously unexported symbol
* comes back as private
*/
p->lt_scenario = SCENARIO_10;
}
/*
* #11, a previously unexported symbol
* stays as unexported
*/
p->lt_scenario = SCENARIO_11;
}
p = p->lt_next;
}
}
/*
* Detects transitions and generates a history of a symbol's classifications
* during integrity checking
* For example; public->private->unexported
*/
static sequence_t *
{
/* category assignments for liblist_t p */
} else {
return (NULL);
}
if (!pat) {
"%s: generate_sequence: calloc: %s\n",
return (NULL);
}
} else {
/*
* If the new item has the same classification as the last item
* on the list, just update the position bitvector
*/
} else {
/* Otherwise, add the new item to the end */
"%s: generate_sequence: calloc: %s\n",
return (NULL);
}
}
}
return (pat);
}
/*
* Walks through the history of a symbol's classifications, assigns scenario #
* based on the algorithm of sequence detection for integrity checking.
*/
static scenario_t
{
int trans_cnt = 0;
int public_bit_in_trans_on = FALSE;
/* category assignments for liblist_t *lib */
while (last) {
trans_cnt ++;
} else break;
}
if (pat) {
if (trans_cnt == 1) {
else
} else if (trans_cnt > 1)
/* mixture of public/unexported transitions */
if (trans_cnt == 1)
else
/* mixture of private/unexported transitions */
if (trans_cnt == 1)
else
/* mixture of public/private/unexported transitions */
if (trans_cnt == 2) {
else
} else if (trans_cnt > 2) {
(public_bit_in_trans_on == TRUE))
else
}
}
}
return (scenario);
}
/*
* Calls generate_sequence() and detect_sequence(),
* it returns a scenario #
*/
static scenario_t
{
}
}
return (scenario);
}
/*
* Perform Integrity Check on the linked list
*/
static void
{
while (p) {
/*
* #1, a new public symbol
* is introduced
*/
p->lt_scenario = SCENARIO_01;
}
/*
* #2, a public symbol exists
* at all releases
*/
p->lt_scenario = SCENARIO_02;
}
/*
* #5, a new private symbol is introduced
*/
p->lt_scenario = SCENARIO_05;
}
/*
* #7, a private symbol exists at all releases
*/
p->lt_scenario = SCENARIO_07;
}
/*
* mixed sequences detected
*/
else {
}
p = p->lt_next;
}
}
/*
* It will do a inorder tree traversal to determine if any symbol is
* unexported from previous Solaris release.
*/
static void
{
if (rootptr) {
}
}
/*
* Walk through the linked list, and check if the symbol disappears
*/
static void
{
while (p) {
if (!oflag) {
p->lt_check_me |= TRUE;
}
}
}
p = p->lt_next;
}
}
/*
* Skips line processing from the intf_check output file if it does not match
* one of the following patterns:
* i.e. SUNW_m.n: _symbol # for FUNCTION types
* SUNW_m.n: _symbol(4) # for OBJECT types
* SUNW_m.n: _symbol (4) # for OBJECT types
* Examples of lines we would skip include:
* - commented lines beginning with '#'
* - lines containing the library name (i.e. the first few lines of
* pvs -dos output)
*/
static int
{
char *lasttoken;
char *first_left_paren;
char *first_right_paren;
int num_spaces;
/*
* skip lines ^#comments, and matching ".so." or ".so:" (i.e., lines
* containing the library name.
*/
(line[0] == '#')) {
return (SUCCEED);
}
return (SUCCEED);
/* check that the 3rd token is surrounded by '()' */
if (first_left_paren == NULL &&
first_right_paren == NULL &&
return (SUCCEED);
}
} else if (num_spaces != 1) {
return (SUCCEED);
}
return (FAIL);
}
/*
* Counts number of times a char "c" occurs in the NULL terminated string "str"
*/
int
count_num_char(const char c, char *str)
{
int count = 0;
while (*str) {
if (*str == c)
count ++;
str ++;
}
return (count);
}
/*
* Converts all occurrences of '=' in the filename to '/'
* i.e. =usr=lib=libfoo.so.1 is translated to /usr/lib/libfoo.so.1
* No error checking since decode_filename() will only be called
* on files found from the intf_check's audit/ directory.
*/
static void
decode_filename(char *filename)
{
while (tmp_path[0] != '\0') {
if (tmp_path[0] == '=') {
*tmp_path = '/';
}
tmp_path ++;
}
}
/*
* Traverses AVL Tree reporting all behaviors
*/
static void
{
if (rootptr) {
}
}
/*
* Detects scenario numbers assigned to each symbol. This will determine
* whether an error message needs to be reported.
*/
static void
{
if (!p)
return;
while (p->st_lib) {
/* detect symbol transitions */
if (!sflag)
} else if ((scenario == SCENARIO_03) ||
(scenario == SCENARIO_12) ||
(scenario == SCENARIO_16) ||
(scenario == SCENARIO_19)) {
} else if (((scenario == SCENARIO_04) ||
(scenario == SCENARIO_13) ||
(scenario == SCENARIO_17)) &&
} else if (((scenario == SCENARIO_08) ||
(scenario == SCENARIO_14) ||
(scenario == SCENARIO_15) ||
(scenario == SCENARIO_18)) &&
if (!sflag)
if (!sflag)
}
}
}
/* Reset p->st_lib for further use of tree pointer */
}
/*
* Reports all errors detected by the scenario number in detect_errors().
* "msg" can be either "ERROR" or "WARNING".
* ERROR will be reported if a symbol transitions from:
* public->unexported
* public->private.
* WARNING messages are only reported when a symbol transitions from:
* private->unexported.
*/
static void
{
int rel;
char *rel_name;
/* Do not report if the symbol was scoped local and never exported */
return;
}
"new public interface introduced\n");
return;
}
/* Integrity checking requires reporting of all releases */
if (iflag) {
rel = 0;
bitmask = get_rel_bitmask(0);
/* Discrepancy checking only reports transitions in last 2 releases */
} else {
}
while (rel < Total_relcnt) {
/*
* For discrepancy checking, reporting will pass through
* this loop twice: e.g. was public in 5.7, is now private
* For integrity checking, we will need to check if transition
* bit is on for each release, and only report in such cases
*/
if (!iflag ||
} else {
}
} else {
}
/* print "->" if there are more transitions */
if (iflag) {
rel_name);
} else {
rel_name);
}
}
rel ++;
}
}
/*
* Adds SCCS keyword, Copyright and customer releases captured
* in ABI database file
*/
static void
{
int i;
time_t t;
"# Copyright %d Sun Microsystems, Inc."
" All rights reserved.\n"
"# Use is subject to license terms.\n"
"#\n"
"#ident\t\"%%"
"Z%%%%"
"M%%\t%%"
"I%%\t%%"
"E%% SMI\"\n"
"#\n"
for (i = 0; i < Total_relcnt - 1; i ++) {
}
}
/*
* Generate ABI Database in ASCII text format:
* <symbol_name> <lib_name> <FUNCTION | OBJECT> <data_size>
* (<highest_lib_version> <base_sym_version>)* <release_bitvector>
* <public_bitvector> <private_bitvector> <unexported_bitvector>
* <evolving_bitvector> <obsolete_bitvector> <scoped_bitvector>
* <unclassified_bitvector>
* SUNW_1.20 SUNW_0.7 11 11 0 0 0 0 0 0
*/
void
{
int index;
char *lib_ver;
char *sym_ver;
} else {
}
} else {
}
}
} else {
}
} else {
}
} else {
}
} else {
}
} else {
}
} else {
}
} else {
}
}
}
/*
* Cleanup routine to close opened files.
*/
static void
cleanup(void)
{
}
}
/*
* read the contents file which records all the libraries under the current
* test directory produced by intf_check.pl, and store them onto a simple
* linked list (actual_libs_pool).
*/
static int
load_liblog(void)
{
char *liblog_file = "/tmp/abi_audit_lib_log";
char line[MAXPATHLEN];
"failed to open <%s>: %s\n",
return (FAIL);
}
line[0] = '\0';
}
}
return (SUCCEED);
}