/* geany_encoding=koi8-r * usefull_macros.h - a set of usefull functions: memory, color etc * * Copyright 2013 Edward V. Emelianoff * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #include // isspace #include #include #include // PATH_MAX #include #include // floor #include #include #include #include #include // flock #include #include #include #include #include #include #include #include "usefull_macros.h" /** * @brief signals - signal handler * @param sig - signal * default signal handler simply exits with `sig` status */ void __attribute__ ((weak)) signals(int sig){ exit(sig); } /** * @brief sl_libversion - version * @return return string with library version */ const char *sl_libversion(){ return PACKAGE_VERSION; } /** * @brief sl_dtime - function for different purposes that need to know time intervals * @return double value: UNIX time in seconds */ double sl_dtime(){ double t; struct timeval tv; gettimeofday(&tv, NULL); t = tv.tv_sec + ((double)tv.tv_usec)/1e6; return t; } /******************************************************************************\ * Coloured terminal \******************************************************************************/ int globErr = 0; // errno for WARN/ERR /** * @brief r_pr_, g_pr_ - format red / green messages * @param fmt ... - printf-like format * @return number of printed symbols */ static int r_pr_(const char *fmt, ...){ va_list ar; int i; printf(COLOR_RED); va_start(ar, fmt); i = vprintf(fmt, ar); va_end(ar); printf(COLOR_OLD); return i; } static int g_pr_(const char *fmt, ...){ va_list ar; int i; printf(COLOR_GREEN); va_start(ar, fmt); i = vprintf(fmt, ar); va_end(ar); printf(COLOR_OLD); return i; } /** * @brief r_WARN - print red error/warning messages (if output is a tty) * @param fmt ... - printf-like format * @return number of printed symbols */ static int r_WARN(const char *fmt, ...){ va_list ar; int i = 1; fprintf(stderr, COLOR_RED); va_start(ar, fmt); if(globErr){ errno = globErr; vwarn(fmt, ar); errno = 0; }else i = vfprintf(stderr, fmt, ar); va_end(ar); i++; fprintf(stderr, COLOR_OLD "\n"); return i; } // pointers to coloured output printf int (*red)(const char *fmt, ...) = r_pr_; int (*green)(const char *fmt, ...) = g_pr_; int (*_WARN)(const char *fmt, ...) = r_WARN; static const char stars[] = "****************************************"; /** * @brief s_WARN, r_pr_notty - notty variants of coloured printf * @param fmt ... - printf-like format * @return number of printed symbols */ static int s_WARN(const char *fmt, ...){ va_list ar; int i; i = fprintf(stderr, "\n%s\n", stars); va_start(ar, fmt); if(globErr){ errno = globErr; vwarn(fmt, ar); errno = 0; }else i = +vfprintf(stderr, fmt, ar); va_end(ar); i += fprintf(stderr, "\n%s\n", stars); i += fprintf(stderr, "\n"); return i; } static int r_pr_notty(const char *fmt, ...){ va_list ar; int i; i = printf("\n%s\n", stars); va_start(ar, fmt); i += vprintf(fmt, ar); va_end(ar); i += printf("\n%s\n", stars); return i; } /** * @brief sl_init - setup locale & console * Run this function in the beginning of main() to setup locale & coloured output */ void sl_init(){ // setup coloured output if(isatty(STDOUT_FILENO)){ // make color output in tty red = r_pr_; green = g_pr_; }else{ // no colors in case of pipe red = r_pr_notty; green = printf; } if(isatty(STDERR_FILENO)) _WARN = r_WARN; else _WARN = s_WARN; // Setup locale setlocale(LC_ALL, ""); setlocale(LC_NUMERIC, "C"); #if defined GETTEXT_PACKAGE && defined LOCALEDIR bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR); textdomain(GETTEXT_PACKAGE); #endif } /******************************************************************************\ * Different things \******************************************************************************/ /** * @brief sl_random_seed - Generate a quasy-random number to initialize PRNG * @return value for srand48 */ long sl_random_seed(){ long r_ini; int fail = 0; int fd = open("/dev/random", O_RDONLY); do{ if(-1 == fd){ WARN(_("Can't open /dev/random")); fail = 1; break; } if(sizeof(long) != read(fd, &r_ini, sizeof(long))){ WARN(_("Can't read /dev/random")); fail = 1; } close(fd); }while(0); if(fail){ double tt = sl_dtime() * 1e6; double mx = (double)LONG_MAX; r_ini = (long)(tt - mx * floor(tt/mx)); } return (r_ini); } /** * @brief sl_mem_avail * @return system available physical memory */ uint64_t sl_mem_avail(){ return sysconf(_SC_AVPHYS_PAGES) * (uint64_t) sysconf(_SC_PAGE_SIZE); } /******************************************************************************\ * Memory \******************************************************************************/ /** * @brief sl_alloc - safe memory allocation for macro ALLOC * @param N - number of elements to allocate * @param S - size of single element (typically sizeof) * @return pointer to allocated memory area */ void *sl_alloc(size_t N, size_t S){ void *p = calloc(N, S); if(!p) ERR("malloc"); //assert(p); return p; } /** * @brief sl_mmap - mmap file to a memory area * @param filename (i) - name of file to mmap * @return stuct with mmap'ed file or die */ sl_mmapbuf_t *sl_mmap(char *filename){ int fd; char *ptr; size_t Mlen; struct stat statbuf; if(!filename){ WARNX(_("No filename given!")); return NULL; } if((fd = open(filename, O_RDONLY)) < 0){ WARN(_("Can't open %s for reading"), filename); return NULL; } if(fstat (fd, &statbuf) < 0){ WARN(_("Can't stat %s"), filename); close(fd); return NULL; } Mlen = statbuf.st_size; if((ptr = mmap (0, Mlen, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED){ WARN(_("Mmap error for input")); close(fd); return NULL; } if(close(fd)) WARN(_("Can't close mmap'ed file")); sl_mmapbuf_t *ret = MALLOC(sl_mmapbuf_t, 1); ret->data = ptr; ret->len = Mlen; return ret; } /** * @brief sl_munmap - unmap memory file * @param b (i) - mmap'ed buffer */ void sl_munmap(sl_mmapbuf_t *b){ if(munmap(b->data, b->len)){ ERR(_("Can't munmap")); } FREE(b); } /** * @brief sl_omitspaces - omit leading spaces * @param v - source string * @return pointer to first non-blank character (could be '\0' if end of string reached) */ char *sl_omitspaces(const char *v){ if(!v) return NULL; char *p = (char*)v; while(*p && isspace(*p)) ++p; return p; } // the same as sl_omitspaces, but return (last non-space char + 1) in string or its first char char *sl_omitspacesr(const char *v){ if(!v) return NULL; char *eol = (char*)v + strlen(v) - 1; while(eol > v && isspace(*eol)) --eol; if(eol == v && isspace(*eol)) return eol; return eol + 1; } /******************************************************************************\ * Terminal in no-echo mode * BE CAREFULL! These functions aren't thread-safe! \******************************************************************************/ // console flags #ifndef SL_USE_OLD_TTY static struct termios2 oldt, newt; #else static struct termios oldt, newt; #endif static int console_changed = 0; /** * @brief sl_restore_con - restore console to default mode */ void sl_restore_con(){ if(console_changed) #ifndef SL_USE_OLD_TTY ioctl(STDIN_FILENO, TCSETS2, &oldt); #else tcsetattr(STDIN_FILENO, TCSANOW, &oldt); // return terminal to previous state #endif console_changed = 0; } /** * @brief sl_setup_con - setup console to non-canonical noecho mode */ void sl_setup_con(){ if(console_changed) return; #ifndef SL_USE_OLD_TTY ioctl(STDIN_FILENO, TCGETS2, &oldt); #else tcgetattr(STDIN_FILENO, &oldt); #endif newt = oldt; newt.c_lflag &= ~(ICANON | ECHO); #ifndef SL_USE_OLD_TTY if(ioctl(STDIN_FILENO, TCSETS2, &newt)){ #else if(tcsetattr(STDIN_FILENO, TCSANOW, &newt) < 0){ #endif WARN(_("Can't setup console")); #ifndef SL_USE_OLD_TTY ioctl(STDIN_FILENO, TCSETS2, &oldt); #else tcsetattr(STDIN_FILENO, TCSANOW, &oldt); #endif signals(1); //quit? } console_changed = 1; } /** * @brief sl_read_con - read character from console without echo * @return char read or zero */ int sl_read_con(){ int rb; struct timeval tv; int retval; fd_set rfds; FD_ZERO(&rfds); FD_SET(STDIN_FILENO, &rfds); tv.tv_sec = 0; tv.tv_usec = 10000; retval = select(1, &rfds, NULL, NULL, &tv); if(!retval) rb = 0; else { if(FD_ISSET(STDIN_FILENO, &rfds)) rb = getchar(); else rb = 0; } return rb; } /** * @brief my - getchar() without echo * wait until at least one character pressed * @return character read */ int sl_getchar(){ int ret; do ret = sl_read_con(); while(ret == 0); return ret; } /** * @brief sl_str2d - safely convert data from string to double * @param num (o) - double number read from string * @param str (i) - input string * @return 1 if success, 0 if fails */ int sl_str2d(double *num, const char *str){ double res; char *endptr; if(!str) return FALSE; res = strtod(str, &endptr); if(endptr == str || *str == '\0' || *endptr != '\0'){ //WARNX(_("Wrong double number format '%s'"), str); return FALSE; } if(num) *num = res; // you may run it like myatod(NULL, str) to test wether str is double number return TRUE; } // and so on int sl_str2ll(long long *num, const char *str){ long long res; char *endptr; if(!str) return FALSE; res = strtoll(str, &endptr, 0); if(endptr == str || *str == '\0' || *endptr != '\0'){ //WARNX(_("Wrong integer number format '%s'")); return FALSE; } if(num) *num = res; return TRUE; } int sl_str2i(int *num, const char *str){ long long res; char *endptr; if(!str) return FALSE; res = strtoll(str, &endptr, 0); if(endptr == str || *str == '\0' || *endptr != '\0'){ //WARNX(_("Wrong integer number format '%s'")); return FALSE; } if(res > INT_MAX || res < INT_MIN){ //WARNX(_("Number out of integer limits")); return FALSE; } if(num) *num = (int)res; return TRUE; } /** * @brief sl_canread - run select() for given fd * @param fd - file descriptor * @return -1 if EINTR, 0 if none, 1 if ready to read */ int sl_canread(int fd){ if(fd < 0) return -1; fd_set fds; struct timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 100; FD_ZERO(&fds); FD_SET(fd, &fds); do{ int rc = select(fd+1, &fds, NULL, NULL, &timeout); if(rc < 0){ if(errno != EINTR){ WARN("select()"); return -1; } continue; } break; }while(1); if(FD_ISSET(fd, &fds)) return 1; return 0; } /** * @brief sl_canwrite - run select() for given fd * @param fd - file descriptor * @return -1 if EINTR, 0 if none, 1 if ready to write without blocking */ int sl_canwrite(int fd){ fd_set fds; struct timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 100; FD_ZERO(&fds); FD_SET(fd, &fds); //DBG("try ability of written to %d", fd); do{ int rc = select(fd+1, NULL, &fds, NULL, &timeout); if(rc < 0){ DBG("-1"); if(errno != EINTR){ LOGWARN("select()"); WARN("select()"); return -1; } continue; } break; }while(1); if(FD_ISSET(fd, &fds)) return 1; return 0; } /******************************************************************************\ * Logging to file \******************************************************************************/ sl_log_t *sl_globlog = NULL; // "global" log file (the first opened logfile) /** * @brief sl_createlog - create log file, test file open ability * @param logpath - path to log file * @param level - lowest message level (e.g. LOGLEVEL_ERR won't allow to write warn/msg/dbg) * @return allocated structure (should be free'd later by Cl_deletelog) or NULL */ sl_log_t *sl_createlog(const char *logpath, sl_loglevel_e level, int prefix){ if(level < LOGLEVEL_NONE || level > LOGLEVEL_ANY) return NULL; if(!logpath) return NULL; FILE *logfd = fopen(logpath, "a"); if(!logfd){ WARN("Can't open log file"); return NULL; } fclose(logfd); sl_log_t *log = MALLOC(sl_log_t, 1); log->logpath = strdup(logpath); if(!log->logpath){ WARN("strdup()"); FREE(log); return NULL; } log->loglevel = level; log->addprefix = prefix; return log; } void sl_deletelog(sl_log_t **log){ if(!log || !*log) return; FREE((*log)->logpath); FREE(*log); } /** * @brief sl_putlog - put message to log file with/without timestamp * @param timest - ==1 to put timestamp * @param log - pointer to log structure * @param lvl - message loglevel (if lvl > loglevel, message won't be printed) * @param fmt - format and the rest part of message * @return amount of symbols saved in file */ int sl_putlogt(int timest, sl_log_t *log, sl_loglevel_e lvl, const char *fmt, ...){ if(!log || !log->logpath) return 0; if(lvl > log->loglevel) return 0; int i = 0; FILE *logfd = fopen(log->logpath, "a+"); if(!logfd) return 0; int lfd = fileno(logfd); // try to lock file double t0 = sl_dtime(); int locked = 0; while(sl_dtime() - t0 < 0.1){ // timeout for 0.1s if(-1 == flock(lfd, LOCK_EX | LOCK_NB)) continue; locked = 1; break; } if(!locked) return 0; // can't lock if(log->addprefix){ const char *p; switch(lvl){ case LOGLEVEL_ERR: p = "[ERR]"; break; case LOGLEVEL_WARN: p = "[WARN]"; break; case LOGLEVEL_MSG: p = "[MSG]"; break; case LOGLEVEL_DBG: p = "[DBG]"; break; default: p = NULL; } if(p) i += fprintf(logfd, "%s\t", p); } if(timest){ char strtm[128]; time_t t = time(NULL); struct tm *curtm = localtime(&t); strftime(strtm, 128, "%Y/%m/%d-%H:%M:%S", curtm); i = fprintf(logfd, "%s", strtm); } i += fprintf(logfd, "\t"); va_list ar; va_start(ar, fmt); i += vfprintf(logfd, fmt, ar); va_end(ar); fseek(logfd, -1, SEEK_CUR); char c; ssize_t r = fread(&c, 1, 1, logfd); if(1 == r){ // add '\n' if there was no newline if(c != '\n') i += fprintf(logfd, "\n"); } flock(lfd, LOCK_UN); fclose(logfd); return i; }