| /* |
| * Copyright (c) 2000 Silicon Graphics, Inc. All Rights Reserved. |
| * AUTHOR : Kent Rogers (from Dave Fenner's original) |
| * CO-PILOT : Rich Logan |
| * DATE STARTED : 05/01/90 (rewritten 1/96) |
| * Copyright (c) 2009-2016 Cyril Hrubis <chrubis@suse.cz> |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of version 2 of the GNU General Public License as |
| * published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it would be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| * |
| * Further, this software is distributed without any warranty that it is |
| * free of the rightful claim of any third person regarding infringement |
| * or the like. Any license provided herein, whether implied or |
| * otherwise, applies only to this software file. Patent licenses, if |
| * any, provided herein do not apply to combinations of this program with |
| * other software, or any other product whatsoever. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with this program; if not, write the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy, |
| * Mountain View, CA 94043, or: |
| * |
| * http://www.sgi.com |
| * |
| * For further information regarding this notice, see: |
| * |
| * http://oss.sgi.com/projects/GenInfo/NoticeExplan/ |
| */ |
| |
| #define _GNU_SOURCE |
| |
| #include <pthread.h> |
| #include <assert.h> |
| #include <errno.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <stdarg.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| |
| #include "test.h" |
| #include "safe_macros.h" |
| #include "usctest.h" |
| #include "ltp_priv.h" |
| #include "tst_ansi_color.h" |
| |
| long TEST_RETURN; |
| int TEST_ERRNO; |
| void *TST_RET_PTR; |
| |
| #define VERBOSE 1 |
| #define NOPASS 3 |
| #define DISCARD 4 |
| |
| #define MAXMESG 80 /* max length of internal messages */ |
| #define USERMESG 2048 /* max length of user message */ |
| #define TRUE 1 |
| #define FALSE 0 |
| |
| /* |
| * EXPAND_VAR_ARGS - Expand the variable portion (arg_fmt) of a result |
| * message into the specified string. |
| * |
| * NOTE (garrcoop): arg_fmt _must_ be the last element in each function |
| * argument list that employs this. |
| */ |
| #define EXPAND_VAR_ARGS(buf, arg_fmt, buf_len) do {\ |
| va_list ap; \ |
| assert(arg_fmt != NULL); \ |
| va_start(ap, arg_fmt); \ |
| vsnprintf(buf, buf_len, arg_fmt, ap); \ |
| va_end(ap); \ |
| assert(strlen(buf) > 0); \ |
| } while (0) |
| |
| #ifndef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP |
| # ifdef __ANDROID__ |
| # define PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP \ |
| PTHREAD_RECURSIVE_MUTEX_INITIALIZER |
| # else |
| /* MUSL: http://www.openwall.com/lists/musl/2017/02/20/5 */ |
| # define PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP { {PTHREAD_MUTEX_RECURSIVE} } |
| # endif |
| #endif |
| |
| static pthread_mutex_t tmutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; |
| |
| static void check_env(void); |
| static void tst_condense(int tnum, int ttype, const char *tmesg); |
| static void tst_print(const char *tcid, int tnum, int ttype, const char *tmesg); |
| |
| static int T_exitval = 0; /* exit value used by tst_exit() */ |
| static int passed_cnt; |
| static int T_mode = VERBOSE; /* flag indicating print mode: VERBOSE, */ |
| /* NOPASS, DISCARD */ |
| |
| static char Warn_mesg[MAXMESG]; /* holds warning messages */ |
| |
| /* |
| * These are used for condensing output when NOT in verbose mode. |
| */ |
| static int Buffered = FALSE; /* TRUE if condensed output is currently */ |
| /* buffered (i.e. not yet printed) */ |
| static char *Last_tcid; /* previous test case id */ |
| static int Last_num; /* previous test case number */ |
| static int Last_type; /* previous test result type */ |
| static char *Last_mesg; /* previous test result message */ |
| |
| int tst_count = 0; |
| |
| /* |
| * These globals must be defined in the test. |
| */ |
| extern char *TCID; /* Test case identifier from the test source */ |
| extern int TST_TOTAL; /* Total number of test cases from the test */ |
| |
| |
| struct pair { |
| const char *name; |
| int val; |
| }; |
| |
| #define PAIR(def) [def] = {.name = #def, .val = def}, |
| #define STRPAIR(key, value) [key] = {.name = value, .val = key}, |
| |
| #define PAIR_LOOKUP(pair_arr, idx) do { \ |
| if (idx < 0 || (size_t)idx >= ARRAY_SIZE(pair_arr) || \ |
| pair_arr[idx].name == NULL) \ |
| return "???"; \ |
| return pair_arr[idx].name; \ |
| } while (0) |
| |
| const char *strttype(int ttype) |
| { |
| static const struct pair ttype_pairs[] = { |
| PAIR(TPASS) |
| PAIR(TFAIL) |
| PAIR(TBROK) |
| PAIR(TCONF) |
| PAIR(TWARN) |
| PAIR(TINFO) |
| }; |
| |
| PAIR_LOOKUP(ttype_pairs, TTYPE_RESULT(ttype)); |
| } |
| |
| #include "errnos.h" |
| #include "signame.h" |
| |
| static void tst_res__(const char *file, const int lineno, int ttype, |
| const char *arg_fmt, ...) |
| { |
| pthread_mutex_lock(&tmutex); |
| |
| char tmesg[USERMESG]; |
| int len = 0; |
| int ttype_result = TTYPE_RESULT(ttype); |
| |
| if (file && (ttype_result != TPASS && ttype_result != TINFO)) |
| len = sprintf(tmesg, "%s:%d: ", file, lineno); |
| EXPAND_VAR_ARGS(tmesg + len, arg_fmt, USERMESG - len); |
| |
| /* |
| * Save the test result type by ORing ttype into the current exit |
| * value (used by tst_exit()). |
| */ |
| T_exitval |= ttype_result; |
| |
| if (ttype_result == TPASS) |
| passed_cnt++; |
| |
| check_env(); |
| |
| /* |
| * Set the test case number and print the results, depending on the |
| * display type. |
| */ |
| if (ttype_result == TWARN || ttype_result == TINFO) { |
| tst_print(TCID, 0, ttype, tmesg); |
| } else { |
| if (tst_count < 0) |
| tst_print(TCID, 0, TWARN, |
| "tst_res(): tst_count < 0 is not valid"); |
| |
| /* |
| * Process each display type. |
| */ |
| switch (T_mode) { |
| case DISCARD: |
| break; |
| case NOPASS: /* filtered by tst_print() */ |
| tst_condense(tst_count + 1, ttype, tmesg); |
| break; |
| default: /* VERBOSE */ |
| tst_print(TCID, tst_count + 1, ttype, tmesg); |
| break; |
| } |
| |
| tst_count++; |
| } |
| |
| pthread_mutex_unlock(&tmutex); |
| } |
| |
| static void tst_condense(int tnum, int ttype, const char *tmesg) |
| { |
| int ttype_result = TTYPE_RESULT(ttype); |
| |
| /* |
| * If this result is the same as the previous result, return. |
| */ |
| if (Buffered == TRUE) { |
| if (strcmp(Last_tcid, TCID) == 0 && Last_type == ttype_result && |
| strcmp(Last_mesg, tmesg) == 0) |
| return; |
| |
| /* |
| * This result is different from the previous result. First, |
| * print the previous result. |
| */ |
| tst_print(Last_tcid, Last_num, Last_type, Last_mesg); |
| free(Last_tcid); |
| free(Last_mesg); |
| } |
| |
| /* |
| * If a file was specified, print the current result since we have no |
| * way of retaining the file contents for comparing with future |
| * results. Otherwise, buffer the current result info for next time. |
| */ |
| Last_tcid = malloc(strlen(TCID) + 1); |
| strcpy(Last_tcid, TCID); |
| Last_num = tnum; |
| Last_type = ttype_result; |
| Last_mesg = malloc(strlen(tmesg) + 1); |
| strcpy(Last_mesg, tmesg); |
| Buffered = TRUE; |
| } |
| |
| void tst_old_flush(void) |
| { |
| NO_NEWLIB_ASSERT("Unknown", 0); |
| |
| pthread_mutex_lock(&tmutex); |
| |
| /* |
| * Print out last line if in NOPASS mode. |
| */ |
| if (Buffered == TRUE && T_mode == NOPASS) { |
| tst_print(Last_tcid, Last_num, Last_type, Last_mesg); |
| Buffered = FALSE; |
| } |
| |
| fflush(stdout); |
| |
| pthread_mutex_unlock(&tmutex); |
| } |
| |
| static void tst_print(const char *tcid, int tnum, int ttype, const char *tmesg) |
| { |
| int err = errno; |
| const char *type; |
| int ttype_result = TTYPE_RESULT(ttype); |
| char message[USERMESG]; |
| size_t size = 0; |
| |
| /* |
| * Save the test result type by ORing ttype into the current exit value |
| * (used by tst_exit()). This is already done in tst_res(), but is |
| * also done here to catch internal warnings. For internal warnings, |
| * tst_print() is called directly with a case of TWARN. |
| */ |
| T_exitval |= ttype_result; |
| |
| /* |
| * If output mode is DISCARD, or if the output mode is NOPASS and this |
| * result is not one of FAIL, BROK, or WARN, just return. This check |
| * is necessary even though we check for DISCARD mode inside of |
| * tst_res(), since occasionally we get to this point without going |
| * through tst_res() (e.g. internal TWARN messages). |
| */ |
| if (T_mode == DISCARD || (T_mode == NOPASS && ttype_result != TFAIL && |
| ttype_result != TBROK |
| && ttype_result != TWARN)) |
| return; |
| |
| /* |
| * Build the result line and print it. |
| */ |
| type = strttype(ttype); |
| |
| if (T_mode == VERBOSE) { |
| size += snprintf(message + size, sizeof(message) - size, |
| "%-8s %4d ", tcid, tnum); |
| } else { |
| size += snprintf(message + size, sizeof(message) - size, |
| "%-8s %4d ", tcid, tnum); |
| } |
| |
| if (size >= sizeof(message)) { |
| printf("%s: %i: line too long\n", __func__, __LINE__); |
| abort(); |
| } |
| |
| if (tst_color_enabled(STDOUT_FILENO)) |
| size += snprintf(message + size, sizeof(message) - size, |
| "%s%s%s : %s", tst_ttype2color(ttype), type, ANSI_COLOR_RESET, tmesg); |
| else |
| size += snprintf(message + size, sizeof(message) - size, |
| "%s : %s", type, tmesg); |
| |
| if (size >= sizeof(message)) { |
| printf("%s: %i: line too long\n", __func__, __LINE__); |
| abort(); |
| } |
| |
| if (ttype & TERRNO) { |
| size += snprintf(message + size, sizeof(message) - size, |
| ": errno=%s(%i): %s", tst_strerrno(err), |
| err, strerror(err)); |
| } |
| |
| if (size >= sizeof(message)) { |
| printf("%s: %i: line too long\n", __func__, __LINE__); |
| abort(); |
| } |
| |
| if (ttype & TTERRNO) { |
| size += snprintf(message + size, sizeof(message) - size, |
| ": TEST_ERRNO=%s(%i): %s", |
| tst_strerrno(TEST_ERRNO), (int)TEST_ERRNO, |
| strerror(TEST_ERRNO)); |
| } |
| |
| if (size >= sizeof(message)) { |
| printf("%s: %i: line too long\n", __func__, __LINE__); |
| abort(); |
| } |
| |
| if (ttype & TRERRNO) { |
| err = TEST_RETURN < 0 ? -(int)TEST_RETURN : (int)TEST_RETURN; |
| size += snprintf(message + size, sizeof(message) - size, |
| ": TEST_RETURN=%s(%i): %s", |
| tst_strerrno(err), err, strerror(err)); |
| } |
| |
| if (size + 1 >= sizeof(message)) { |
| printf("%s: %i: line too long\n", __func__, __LINE__); |
| abort(); |
| } |
| |
| message[size] = '\n'; |
| message[size + 1] = '\0'; |
| |
| fputs(message, stdout); |
| } |
| |
| static void check_env(void) |
| { |
| static int first_time = 1; |
| char *value; |
| |
| if (!first_time) |
| return; |
| |
| first_time = 0; |
| |
| /* BTOUTPUT not defined, use default */ |
| if ((value = getenv(TOUTPUT)) == NULL) { |
| T_mode = VERBOSE; |
| return; |
| } |
| |
| if (strcmp(value, TOUT_NOPASS_S) == 0) { |
| T_mode = NOPASS; |
| return; |
| } |
| |
| if (strcmp(value, TOUT_DISCARD_S) == 0) { |
| T_mode = DISCARD; |
| return; |
| } |
| |
| T_mode = VERBOSE; |
| return; |
| } |
| |
| void tst_exit(void) |
| { |
| NO_NEWLIB_ASSERT("Unknown", 0); |
| |
| pthread_mutex_lock(&tmutex); |
| |
| tst_old_flush(); |
| |
| T_exitval &= ~TINFO; |
| |
| if (T_exitval == TCONF && passed_cnt) |
| T_exitval &= ~TCONF; |
| |
| exit(T_exitval); |
| } |
| |
| pid_t tst_fork(void) |
| { |
| pid_t child; |
| |
| NO_NEWLIB_ASSERT("Unknown", 0); |
| |
| tst_old_flush(); |
| |
| child = fork(); |
| if (child == 0) |
| T_exitval = 0; |
| |
| return child; |
| } |
| |
| void tst_record_childstatus(void (*cleanup)(void), pid_t child) |
| { |
| int status, ttype_result; |
| |
| NO_NEWLIB_ASSERT("Unknown", 0); |
| |
| SAFE_WAITPID(cleanup, child, &status, 0); |
| |
| if (WIFEXITED(status)) { |
| ttype_result = WEXITSTATUS(status); |
| ttype_result = TTYPE_RESULT(ttype_result); |
| T_exitval |= ttype_result; |
| |
| if (ttype_result == TPASS) |
| tst_resm(TINFO, "Child process returned TPASS"); |
| |
| if (ttype_result & TFAIL) |
| tst_resm(TINFO, "Child process returned TFAIL"); |
| |
| if (ttype_result & TBROK) |
| tst_resm(TINFO, "Child process returned TBROK"); |
| |
| if (ttype_result & TCONF) |
| tst_resm(TINFO, "Child process returned TCONF"); |
| |
| } else { |
| tst_brkm(TBROK, cleanup, "child process(%d) killed by " |
| "unexpected signal %s(%d)", child, |
| tst_strsig(WTERMSIG(status)), WTERMSIG(status)); |
| } |
| } |
| |
| pid_t tst_vfork(void) |
| { |
| NO_NEWLIB_ASSERT("Unknown", 0); |
| |
| tst_old_flush(); |
| return vfork(); |
| } |
| |
| /* |
| * Make tst_brk reentrant so that one can call the SAFE_* macros from within |
| * user-defined cleanup functions. |
| */ |
| static int tst_brk_entered = 0; |
| |
| static void tst_brk__(const char *file, const int lineno, int ttype, |
| void (*func)(void), const char *arg_fmt, ...) |
| { |
| pthread_mutex_lock(&tmutex); |
| |
| char tmesg[USERMESG]; |
| int ttype_result = TTYPE_RESULT(ttype); |
| |
| EXPAND_VAR_ARGS(tmesg, arg_fmt, USERMESG); |
| |
| /* |
| * Only FAIL, BROK, CONF, and RETR are supported by tst_brk(). |
| */ |
| if (ttype_result != TFAIL && ttype_result != TBROK && |
| ttype_result != TCONF) { |
| sprintf(Warn_mesg, "%s: Invalid Type: %d. Using TBROK", |
| __func__, ttype_result); |
| tst_print(TCID, 0, TWARN, Warn_mesg); |
| /* Keep TERRNO, TTERRNO, etc. */ |
| ttype = (ttype & ~ttype_result) | TBROK; |
| } |
| |
| tst_res__(file, lineno, ttype, "%s", tmesg); |
| if (tst_brk_entered == 0) { |
| if (ttype_result == TCONF) { |
| tst_res__(file, lineno, ttype, |
| "Remaining cases not appropriate for " |
| "configuration"); |
| } else if (ttype_result == TBROK) { |
| tst_res__(file, lineno, TBROK, |
| "Remaining cases broken"); |
| } |
| } |
| |
| /* |
| * If no cleanup function was specified, just return to the caller. |
| * Otherwise call the specified function. |
| */ |
| if (func != NULL) { |
| tst_brk_entered++; |
| (*func) (); |
| tst_brk_entered--; |
| } |
| if (tst_brk_entered == 0) |
| tst_exit(); |
| |
| pthread_mutex_unlock(&tmutex); |
| } |
| |
| void tst_resm_(const char *file, const int lineno, int ttype, |
| const char *arg_fmt, ...) |
| { |
| char tmesg[USERMESG]; |
| |
| EXPAND_VAR_ARGS(tmesg, arg_fmt, USERMESG); |
| |
| if (tst_test) |
| tst_res_(file, lineno, ttype, "%s", tmesg); |
| else |
| tst_res__(file, lineno, ttype, "%s", tmesg); |
| } |
| |
| typedef void (*tst_res_func_t)(const char *file, const int lineno, |
| int ttype, const char *fmt, ...); |
| |
| void tst_resm_hexd_(const char *file, const int lineno, int ttype, |
| const void *buf, size_t size, const char *arg_fmt, ...) |
| { |
| char tmesg[USERMESG]; |
| static const size_t symb_num = 2; /* xx */ |
| static const size_t size_max = 16; |
| size_t offset; |
| size_t i; |
| char *pmesg = tmesg; |
| tst_res_func_t res_func; |
| |
| if (tst_test) |
| res_func = tst_res_; |
| else |
| res_func = tst_res__; |
| |
| EXPAND_VAR_ARGS(tmesg, arg_fmt, USERMESG); |
| offset = strlen(tmesg); |
| |
| if (size > size_max || size == 0 || |
| (offset + size * (symb_num + 1)) >= USERMESG) |
| res_func(file, lineno, ttype, "%s", tmesg); |
| else |
| pmesg += offset; |
| |
| for (i = 0; i < size; ++i) { |
| /* add space before byte except first one */ |
| if (pmesg != tmesg) |
| *(pmesg++) = ' '; |
| |
| sprintf(pmesg, "%02x", ((unsigned char *)buf)[i]); |
| pmesg += symb_num; |
| if ((i + 1) % size_max == 0 || i + 1 == size) { |
| res_func(file, lineno, ttype, "%s", tmesg); |
| pmesg = tmesg; |
| } |
| } |
| } |
| |
| void tst_brkm_(const char *file, const int lineno, int ttype, |
| void (*func)(void), const char *arg_fmt, ...) |
| { |
| char tmesg[USERMESG]; |
| |
| EXPAND_VAR_ARGS(tmesg, arg_fmt, USERMESG); |
| |
| if (tst_test) { |
| if (func) { |
| tst_brk_(file, lineno, TBROK, |
| "Non-NULL cleanup in newlib!"); |
| } |
| |
| tst_brk_(file, lineno, ttype, "%s", tmesg); |
| } else { |
| tst_brk__(file, lineno, ttype, func, "%s", tmesg); |
| } |
| |
| /* Shouldn't be reached, but fixes build time warnings about noreturn. */ |
| abort(); |
| } |
| |
| void tst_require_root(void) |
| { |
| NO_NEWLIB_ASSERT("Unknown", 0); |
| |
| if (geteuid() != 0) |
| tst_brkm(TCONF, NULL, "Test needs to be run as root"); |
| } |