/
githubmirror
/
rsyslog
Обзор
Документация
Войти
/
githubmirror
/
rsyslog
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
runtime/srutils.c
867 строк
28 KB
Rainer Gerhards
testbench: enable proper termination markers by default
27 июн 2026, 17:03
27 июн 2026, 17:03
cbd28c1
Код
Авторство
О чём код?
/**\file srUtils.c * \brief General utilties that fit nowhere else. * * The namespace for this file is "srUtil". * * \author Rainer Gerhards <rgerhards@adiscon.com> * \date 2003-09-09 * Coding begun. * * Copyright 2003-2026 Rainer Gerhards and Adiscon GmbH. * * This file is part of the rsyslog runtime library. * * The rsyslog runtime library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The rsyslog runtime library 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. * * A copy of the GPL can be found in the file "COPYING" in this distribution. * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. */ #include "config.h" #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <sys/stat.h> #include <sys/types.h> #include <signal.h> #include <assert.h> #include <limits.h> #include <sys/wait.h> #include <ctype.h> #include <inttypes.h> #include <fcntl.h> #include "rsyslog.h" #include "srUtils.h" #include "obj.h" #include "errmsg.h" #include "glbl.h" #include "rsconf.h" #include "unicode-helper.h" #if _POSIX_TIMERS <= 0 #include <sys/time.h> #endif /* here we host some syslog specific names. There currently is no better place * to do it, but over here is also not ideal... -- rgerhards, 2008-02-14 * rgerhards, 2008-04-16: note in LGPL move: the code tables below exist in * the same way in BSD, so it is not a problem to move them from GPLv3 to LGPL. * And nobody modified them since it was under LGPL, so we can also move it * to ASL 2.0. */ syslogName_t syslogPriNames[] = { {"alert", LOG_ALERT}, {"crit", LOG_CRIT}, {"debug", LOG_DEBUG}, {"emerg", LOG_EMERG}, {"err", LOG_ERR}, {"error", LOG_ERR}, /* DEPRECATED */ {"info", LOG_INFO}, {"none", INTERNAL_NOPRI}, /* INTERNAL */ {"notice", LOG_NOTICE}, {"panic", LOG_EMERG}, /* DEPRECATED */ {"warn", LOG_WARNING}, /* DEPRECATED */ {"warning", LOG_WARNING}, {"*", TABLE_ALLPRI}, {NULL, -1}}; #ifndef LOG_AUTHPRIV #define LOG_AUTHPRIV LOG_AUTH #endif syslogName_t syslogFacNames[] = { {"auth", LOG_AUTH}, {"authpriv", LOG_AUTHPRIV}, {"cron", LOG_CRON}, {"daemon", LOG_DAEMON}, {"kern", LOG_KERN}, {"lpr", LOG_LPR}, {"mail", LOG_MAIL}, {"mark", LOG_MARK}, /* INTERNAL */ {"news", LOG_NEWS}, {"ntp", (12 << 3)}, /* NTP, perhaps BSD-specific? */ {"security", LOG_AUTH}, /* DEPRECATED */ {"bsd_security", (13 << 3)}, /* BSD-specific, unfortunatly with duplicate name... */ {"syslog", LOG_SYSLOG}, {"user", LOG_USER}, {"uucp", LOG_UUCP}, #if defined(_AIX) /* AIXPORT : These are necessary for AIX */ {"caa", LOG_CAA}, {"aso", LOG_ASO}, #endif #if defined(LOG_FTP) {"ftp", LOG_FTP}, #endif #if defined(LOG_AUDIT) {"audit", LOG_AUDIT}, #endif {"console", (14 << 3)}, /* BSD-specific priority */ {"local0", LOG_LOCAL0}, {"local1", LOG_LOCAL1}, {"local2", LOG_LOCAL2}, {"local3", LOG_LOCAL3}, {"local4", LOG_LOCAL4}, {"local5", LOG_LOCAL5}, {"local6", LOG_LOCAL6}, {"local7", LOG_LOCAL7}, {"invld", LOG_INVLD}, {NULL, -1}, }; /* ################################################################# * * private members * * ################################################################# */ /* As this is not a "real" object, there won't be any private * members in this file. */ /* ################################################################# * * public members * * ################################################################# */ rsRetVal srUtilItoA(char *pBuf, int iLenBuf, number_t iToConv) { int i; int bIsNegative; char szBuf[64]; /* sufficiently large for my lifespan and those of my children... ;) */ assert(pBuf != NULL); assert(iLenBuf > 1); /* This is actually an app error and as thus checked for... */ if (iToConv < 0) { bIsNegative = RSTRUE; iToConv *= -1; } else bIsNegative = RSFALSE; /* first generate a string with the digits in the reverse direction */ i = 0; do { szBuf[i++] = iToConv % 10 + '0'; iToConv /= 10; } while (iToConv > 0); /* warning: do...while()! */ --i; /* undo last increment - we were pointing at NEXT location */ /* make sure we are within bounds... */ if (i + 2 > iLenBuf) /* +2 because: a) i starts at zero! b) the \0 byte */ return RS_RET_PROVIDED_BUFFER_TOO_SMALL; /* then move it to the right direction... */ if (bIsNegative == RSTRUE) *pBuf++ = '-'; while (i >= 0) *pBuf++ = szBuf[i--]; *pBuf = '\0'; /* terminate it!!! */ return RS_RET_OK; } uchar *srUtilStrDup(uchar *pOld, size_t len) { uchar *pNew; assert(pOld != NULL); if ((pNew = malloc(len + 1)) != NULL) memcpy(pNew, pOld, len + 1); return pNew; } /* creates a path recursively * Return 0 on success, -1 otherwise. On failure, errno * hold the last OS error. * Param "mode" holds the mode that all non-existing directories are to be * created with. * Note that we have a potential race inside that code, a race that even exists * outside of the rsyslog process (if multiple instances run, or other programs * generate directories): If the directory does not exist, a context switch happens, * at that moment another process creates it, then our creation on the context * switch back fails. This actually happened in practice, and depending on the * configuration it is even likely to happen. We can not solve this situation * with a mutex, as that works only within out process space. So the solution * is that we take the optimistic approach, try the creation, and if it fails * with "already exists" we go back and do one retry of the check/create * sequence. That should then succeed. If the directory is still not found but * the creation fails in the similar way, we return an error on that second * try because otherwise we would potentially run into an endless loop. * loop. -- rgerhards, 2010-03-25 * The likeliest scenario for a prolonged contest of creating the parent directiories * is within our process space. This can happen with a high probability when two * threads, that want to start logging to files within same directory tree, are * started close to each other. We should fix what we can. -- nipakoo, 2017-11-25 */ static int real_makeFileParentDirs(const uchar *const szFile, const size_t lenFile, const mode_t mode, const uid_t uid, const gid_t gid, const int bFailOnChownFail) { uchar *p; uchar *pszWork; size_t len; assert(szFile != NULL); assert(lenFile > 0); len = lenFile + 1; /* add one for '\0'-byte */ if ((pszWork = malloc(len)) == NULL) return -1; memcpy(pszWork, szFile, len); for (p = pszWork + 1; *p; p++) if (*p == '/') { /* temporarily terminate string, create dir and go on */ *p = '\0'; int bErr = 0; if (mkdir((char *)pszWork, mode) == 0) { if (uid != (uid_t)-1 || gid != (gid_t)-1) { /* we need to set owner/group */ if (chown((char *)pszWork, uid, gid) != 0) { LogError(errno, RS_RET_DIR_CHOWN_ERROR, "chown for directory '%s' failed", pszWork); if (bFailOnChownFail) { /* ignore if configured to do so */ bErr = 1; } } } } else if (errno != EEXIST) { /* EEXIST is ok, means this component exists */ bErr = 1; } if (bErr) { int eSave = errno; free(pszWork); errno = eSave; return -1; } *p = '/'; } free(pszWork); return 0; } /* note: this small function is the stub for the brain-dead POSIX cancel handling */ int makeFileParentDirs(const uchar *const szFile, const size_t lenFile, const mode_t mode, const uid_t uid, const gid_t gid, const int bFailOnChownFail) { static pthread_mutex_t mutParentDir = PTHREAD_MUTEX_INITIALIZER; int r; /* needs to be declared OUTSIDE of pthread_cleanup... macros! */ pthread_mutex_lock(&mutParentDir); pthread_cleanup_push(mutexCancelCleanup, &mutParentDir); r = real_makeFileParentDirs(szFile, lenFile, mode, uid, gid, bFailOnChownFail); pthread_mutex_unlock(&mutParentDir); pthread_cleanup_pop(0); return r; } /** * @brief Execute a program with up to two optional arguments. * * This helper forks and then execs `program`. In the child process it resets * all signal handlers to defaults and clears any pending alarm before exec. * That behavior is intentional to avoid inheriting rsyslogd signal handling * into the external command. * * Arguments are optional and passed as-is (no shell parsing). If @p arg1 is * NULL and @p arg2 is non-NULL, @p arg2 is shifted into the first argument * position so callers can pass either only arg2 or both without extra logic. * * If bWait is set, the parent will wait for the child to exit and record * its status via glblReportChildProcessExit(). If not set, the child is * allowed to continue independently. * * @param program Executable name/path to run. * @param bWait If non-zero, wait for the child to exit before returning. * @param arg1 Optional first argument (may be NULL). * @param arg2 Optional second argument (may be NULL). * @return Child PID on success, 0 on fork failure (errno set). */ int execProg(uchar *program, int bWait, uchar *arg1, uchar *arg2) { int pid; int sig; struct sigaction sigAct; char *exec_argv[4]; int argc = 1; exec_argv[0] = (char *)program; if (arg1 == NULL && arg2 != NULL) { arg1 = arg2; arg2 = NULL; } if (arg1 != NULL) exec_argv[argc++] = (char *)arg1; if (arg2 != NULL) exec_argv[argc++] = (char *)arg2; exec_argv[argc] = NULL; dbgprintf("exec program '%s' with %d args\n", program, argc - 1); pid = fork(); if (pid < 0) { return 0; } if (pid) { /* Parent */ if (bWait) { /* waitpid will fail with errno == ECHILD if the child process has already been reaped by the rsyslogd main loop (see rsyslogd.c) */ int status; if (waitpid(pid, &status, 0) == pid) { glblReportChildProcessExit(runConf, program, pid, status); } else if (errno != ECHILD) { /* we do not use logerror(), because * that might bring us into an endless * loop. At some time, we may * reconsider this behaviour. */ dbgprintf("could not wait on child after executing '%s'", (char *)program); } } return pid; } /* Child */ alarm(0); /* create a clean environment before we exec the real child */ memset(&sigAct, 0, sizeof(sigAct)); sigemptyset(&sigAct.sa_mask); sigAct.sa_handler = SIG_DFL; for (sig = 1; sig < NSIG; ++sig) sigaction(sig, &sigAct, NULL); execvp((char *)program, exec_argv); /* In the long term, it's a good idea to implement some enhanced error * checking here. However, it can not easily be done. For starters, we * may run into endless loops if we log to syslog. The next problem is * that output is typically not seen by the user. For the time being, * we use no error reporting, which is quite consitent with the old * system() way of doing things. rgerhards, 2007-07-20 */ perror("exec"); fprintf(stderr, "exec program was '%s'\n", program); exit(1); /* not much we can do in this case */ } /* skip over whitespace in a standard C string. The * provided pointer is advanced to the first non-whitespace * charater or the \0 byte, if there is none. It is never * moved past the \0. */ void skipWhiteSpace(uchar **pp) { register uchar *p; assert(pp != NULL); assert(*pp != NULL); p = *pp; while (*p && isspace((int)*p)) ++p; *pp = p; } /* generate a file name from four parts: * <directory name>/<name>.<number> * If number is negative, it is not used. If any of the strings is * NULL, an empty string is used instead. Length must be provided. * lNumDigits is the minimum number of digits that lNum should have. This * is to pretty-print the file name, e.g. lNum = 3, lNumDigits= 4 will * result in "0003" being used inside the file name. Set lNumDigits to 0 * to use as few space as possible. * rgerhards, 2008-01-03 */ PRAGMA_DIAGNOSTIC_PUSH PRAGMA_IGNORE_Wformat_nonliteral rsRetVal genFileName( uchar **ppName, uchar *pDirName, size_t lenDirName, uchar *pFName, size_t lenFName, int64_t lNum, int lNumDigits) { DEFiRet; uchar *pName; uchar *pNameWork; size_t lenName; uchar szBuf[128]; /* buffer for number */ char szFmtBuf[32]; /* buffer for snprintf format */ size_t lenBuf; if (lNum < 0) { szBuf[0] = '\0'; lenBuf = 0; } else { if (lNumDigits > 0) { snprintf(szFmtBuf, sizeof(szFmtBuf), ".%%0%d" PRId64, lNumDigits); lenBuf = snprintf((char *)szBuf, sizeof(szBuf), szFmtBuf, lNum); } else lenBuf = snprintf((char *)szBuf, sizeof(szBuf), ".%" PRId64, lNum); } lenName = lenDirName + 1 + lenFName + lenBuf + 1; /* last +1 for \0 char! */ if ((pName = malloc(lenName)) == NULL) ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); /* got memory, now construct string */ memcpy(pName, pDirName, lenDirName); pNameWork = pName + lenDirName; *pNameWork++ = '/'; memcpy(pNameWork, pFName, lenFName); pNameWork += lenFName; if (lenBuf > 0) { memcpy(pNameWork, szBuf, lenBuf); pNameWork += lenBuf; } *pNameWork = '\0'; *ppName = pName; finalize_it: RETiRet; } PRAGMA_DIAGNOSTIC_POP /* get the number of digits required to represent a given number. We use an * iterative approach as we do not like to draw in the floating point * library just for log(). -- rgerhards, 2008-01-10 */ int getNumberDigits(long lNum) { int iDig; if (lNum == 0) iDig = 1; else for (iDig = 0; lNum != 0; ++iDig) lNum /= 10; return iDig; } /* compute an absolute time timeout suitable for calls to pthread_cond_timedwait() * iTimeout is in milliseconds * rgerhards, 2008-01-14 */ rsRetVal timeoutComp(struct timespec *pt, long iTimeout) { #if _POSIX_TIMERS <= 0 struct timeval tv; #endif assert(pt != NULL); /* compute timeout */ #if _POSIX_TIMERS > 0 /* this is the "regular" code */ clock_gettime(CLOCK_REALTIME, pt); #else gettimeofday(&tv, NULL); pt->tv_sec = tv.tv_sec; pt->tv_nsec = tv.tv_usec * 1000; #endif pt->tv_sec += iTimeout / 1000; pt->tv_nsec += (iTimeout % 1000) * 1000000; /* think INTEGER arithmetic! */ if (pt->tv_nsec > 999999999) { /* overrun? */ pt->tv_nsec -= 1000000000; ++pt->tv_sec; } return RS_RET_OK; /* so far, this is static... */ } long long currentTimeMills(void) { struct timespec tm; #if _POSIX_TIMERS <= 0 struct timeval tv; #endif #if _POSIX_TIMERS > 0 clock_gettime(CLOCK_REALTIME, &tm); #else gettimeofday(&tv, NULL); tm.tv_sec = tv.tv_sec; tm.tv_nsec = tv.tv_usec * 1000; #endif return ((long long)tm.tv_sec) * 1000 + (tm.tv_nsec / 1000000); } /* This function is kind of the reverse of timeoutComp() - it takes an absolute * timeout value and computes how far this is in the future. If the value is already * in the past, 0 is returned. The return value is in ms. * rgerhards, 2008-01-25 */ long timeoutVal(struct timespec *pt) { struct timespec t; long iTimeout; #if _POSIX_TIMERS <= 0 struct timeval tv; #endif assert(pt != NULL); /* compute timeout */ #if _POSIX_TIMERS > 0 /* this is the "regular" code */ clock_gettime(CLOCK_REALTIME, &t); #else gettimeofday(&tv, NULL); t.tv_sec = tv.tv_sec; t.tv_nsec = tv.tv_usec * 1000; #endif iTimeout = (pt->tv_nsec - t.tv_nsec) / 1000000; iTimeout += (pt->tv_sec - t.tv_sec) * 1000; if (iTimeout < 0) iTimeout = 0; return iTimeout; } /* cancellation cleanup handler - frees provided mutex * rgerhards, 2008-01-14 */ void mutexCancelCleanup(void *arg) { assert(arg != NULL); d_pthread_mutex_unlock((pthread_mutex_t *)arg); } /* rsSleep() - a fairly portable way to to sleep. It * will wake up when * a) the wake-time is over * rgerhards, 2008-01-28 */ void srSleep(int iSeconds, int iuSeconds) { struct timeval tvSelectTimeout; tvSelectTimeout.tv_sec = iSeconds; tvSelectTimeout.tv_usec = iuSeconds; /* micro seconds */ select(0, NULL, NULL, NULL, &tvSelectTimeout); } /* From varmojfekoj's mail on why he provided rs_strerror_r(): * There are two problems with strerror_r(): * I see you've rewritten some of the code which calls it to use only * the supplied buffer; unfortunately the GNU implementation sometimes * doesn't use the buffer at all and returns a pointer to some * immutable string instead, as noted in the man page. * * The other problem is that on some systems strerror_r() has a return * type of int. * * So I've written a wrapper function rs_strerror_r(), which should * take care of all this and be used instead. * * Added 2008-01-30 */ char *rs_strerror_r(int errnum, char *buf, size_t buflen) { #ifndef HAVE_STRERROR_R char *pszErr; pszErr = strerror(errnum); if (buflen > 0) { rs_cstr_copy(buf, pszErr, buflen); } #else #ifdef STRERROR_R_CHAR_P char *p = strerror_r(errnum, buf, buflen); if (p != buf) { if (buflen > 0) { rs_cstr_copy(buf, p, buflen); } } #else strerror_r(errnum, buf, buflen); #endif #endif /* #ifdef __hpux */ return buf; } /* Decode a symbolic name to a numeric value */ int decodeSyslogName(uchar *name, syslogName_t *codetab) { register syslogName_t *c; register uchar *p; char *end = NULL; long val; uchar buf[80]; assert(name != NULL); assert(codetab != NULL); DBGPRINTF("symbolic name: %s", name); if (isdigit((int)*name)) { DBGPRINTF("\n"); errno = 0; val = strtol((char *)name, &end, 10); if (errno != 0 || end == (char *)name || *end != '\0' || val < INT_MIN || val > INT_MAX) { return -1; } return (int)val; } u_cstr_copy(buf, name, sizeof(buf)); for (p = buf; *p; p++) { if (isupper((int)*p)) *p = tolower((int)*p); } for (c = codetab; c->c_name; c++) { if (!strcmp((char *)buf, (char *)c->c_name)) { DBGPRINTF(" ==> %d\n", c->c_val); return (c->c_val); } } DBGPRINTF("\n"); return (-1); } /** * getSubString * * Copy a string byte by byte until the occurrence * of a given separator. * * \param ppSrc Pointer to a pointer of the source array of characters. If a separator detected the Pointer points to the next char after the separator. Except if the end of the string is dedected ('\n'). Then it points to the terminator char. * \param pDst Pointer to the destination array of characters. Here the substing will be stored. * \param DstSize Maximum numbers of characters to store. * \param cSep Separator char. * \ret int Returns 0 if no error occurred. * * rgerhards, 2008-02-12: some notes are due... I will once again fix this function, this time * so that it treats ' ' as a request for whitespace. But in general, the function and its callers * should be changed over time, this is not really very good code... */ int getSubString(uchar **ppSrc, char *pDst, size_t DstSize, char cSep) { uchar *pSrc = *ppSrc; int iErr = 0; /* 0 = no error, >0 = error */ while ((cSep == ' ' ? !isspace(*pSrc) : *pSrc != cSep) && *pSrc != '\n' && *pSrc != '\0' && DstSize > 1) { *pDst++ = *(pSrc)++; DstSize--; } /* check if the Dst buffer was to small */ if ((cSep == ' ' ? !isspace(*pSrc) : *pSrc != cSep) && *pSrc != '\n' && *pSrc != '\0') { dbgprintf("in getSubString, error Src buffer > Dst buffer\n"); iErr = 1; } if (*pSrc == '\0' || *pSrc == '\n') /* this line was missing, causing ppSrc to be invalid when it * was returned in case of end-of-string. rgerhards 2005-07-29 */ *ppSrc = pSrc; else *ppSrc = pSrc + 1; *pDst = '\0'; return iErr; } /* get the size of a file or return appropriate error code. If an error is returned, * *pSize content is undefined. * rgerhards, 2009-06-12 */ rsRetVal getFileSize(uchar *pszName, off_t *pSize) { int ret; struct stat statBuf; DEFiRet; ret = stat((char *)pszName, &statBuf); if (ret == -1) { switch (errno) { case EACCES: ABORT_FINALIZE(RS_RET_NO_FILE_ACCESS); case ENOTDIR: case ENOENT: ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND); default: ABORT_FINALIZE(RS_RET_FILE_NO_STAT); } } *pSize = statBuf.st_size; finalize_it: RETiRet; } /* Returns 1 if the given string contains a non-escaped glob(3) * wildcard character and 0 otherwise (or if the string is empty). */ int containsGlobWildcard(char *str) { char *p; if (!str) { return 0; } /* From Linux Programmer's Guide: * "A string is a wildcard pattern if it contains one of the characters '?', '*', '{' or '['" * "One can remove the special meaning of '?', '*', '{' and '[' by preceding them by a backslash" */ for (p = str; *p != '\0'; p++) { if ((*p == '?' || *p == '*' || *p == '[' || *p == '{') && (p == str || *(p - 1) != '\\')) { return 1; } } return 0; } static void seedRandomInsecureNumber(void) { struct timespec t; timeoutComp(&t, 0); long long x = t.tv_sec * 3 + t.tv_nsec * 2; srandom((unsigned int)x); } static long int randomInsecureNumber(void) { return random(); } #ifdef OS_LINUX static int fdURandom = -1; void seedRandomNumber(void) { if (fdURandom >= 0) { /* Already opened. */ return; } fdURandom = open("/dev/urandom", O_RDONLY); if (fdURandom == -1) { LogError(errno, RS_RET_IO_ERROR, "failed to seed random number generation," " will use fallback (open urandom failed)"); seedRandomInsecureNumber(); } } void seedRandomNumberForChild(void) { /* The file descriptor inherited from our parent will have been closed after * the fork. Discard this and call seedRandomNumber() to open /dev/urandom * again. */ fdURandom = -1; seedRandomNumber(); } long int randomNumber(void) { long int ret; if (fdURandom >= 0) { if (read(fdURandom, &ret, sizeof(long int)) == -1) { LogError(errno, RS_RET_IO_ERROR, "failed to generate random number, will" " use fallback (read urandom failed)"); ret = randomInsecureNumber(); } } else { ret = randomInsecureNumber(); } return ret; } #else void seedRandomNumber(void) { seedRandomInsecureNumber(); } void seedRandomNumberForChild(void) { seedRandomNumber(); } long int randomNumber(void) { return randomInsecureNumber(); } #endif /* process "binary" parameters where this is needed to execute * programs (namely mmexternal and omprog). * Most importantly, split them into argv[] and get the binary name */ rsRetVal ATTR_NONNULL() split_binary_parameters(uchar **const szBinary, char ***const __restrict__ aParams, int *const iParams, es_str_t *const param_binary) { es_size_t iCnt; es_size_t iStr; int iPrm; es_str_t *estrParams = NULL; es_str_t *estrBinary = param_binary; es_str_t *estrTmp = NULL; uchar *c; int bInQuotes; DEFiRet; assert(iParams != NULL); assert(param_binary != NULL); /* Search for end of binary name */ c = es_getBufAddr(param_binary); iCnt = 0; while (iCnt < es_strlen(param_binary)) { if (c[iCnt] == ' ') { /* Split binary name from parameters */ estrBinary = es_newStrFromSubStr(param_binary, 0, iCnt); estrParams = es_newStrFromSubStr(param_binary, iCnt + 1, es_strlen(param_binary)); break; } iCnt++; } *szBinary = (uchar *)es_str2cstr(estrBinary, NULL); DBGPRINTF("szBinary = '%s'\n", *szBinary); *iParams = 1; /* we always have argv[0] */ /* count size of argv[] */ if (estrParams != NULL) { (*iParams)++; /* last parameter is not counted in loop below! */ if (Debug) { char *params = es_str2cstr(estrParams, NULL); dbgprintf("szParams = '%s'\n", params); free(params); } c = es_getBufAddr(estrParams); for (iCnt = 0; iCnt < es_strlen(estrParams); ++iCnt) { if (c[iCnt] == ' ' && c[iCnt - 1] != '\\') (*iParams)++; } } DBGPRINTF("iParams %d (+1 for NULL terminator)\n", *iParams); /* create argv[] */ CHKmalloc(*aParams = calloc(*iParams + 1, sizeof(char *))); iPrm = 0; bInQuotes = FALSE; /* Set first parameter to binary */ (*aParams)[iPrm] = strdup((char *)*szBinary); iPrm++; if (estrParams != NULL) { iCnt = iStr = 0; c = es_getBufAddr(estrParams); /* Reset to beginning */ while (iCnt < es_strlen(estrParams)) { if (c[iCnt] == '"' && iCnt == iStr && !bInQuotes) { bInQuotes = TRUE; iStr++; } else { int bEOL = iCnt + 1 == es_strlen(estrParams); int bSpace = c[iCnt] == ' '; int bQuoteEnd = bInQuotes && ((bSpace && c[iCnt - 1] == '"') || (c[iCnt] == '"' && bEOL)); if (bEOL || bQuoteEnd || (bSpace && !bInQuotes)) { int iSubCnt = iCnt - iStr; if (bEOL) iSubCnt++; if (bQuoteEnd) iSubCnt--; estrTmp = es_newStrFromSubStr(estrParams, iStr, iSubCnt); } if (bQuoteEnd) bInQuotes = FALSE; } if (estrTmp != NULL) { (*aParams)[iPrm] = es_str2cstr(estrTmp, NULL); iStr = iCnt + 1; /* Set new start */ DBGPRINTF("Param (%d): '%s'\n", iPrm, (*aParams)[iPrm]); es_deleteStr(estrTmp); estrTmp = NULL; iPrm++; } iCnt++; } } (*aParams)[iPrm] = NULL; /* NULL per argv[] convention */ finalize_it: if (estrBinary != param_binary) { es_deleteStr(estrBinary); } if (estrParams != NULL) { es_deleteStr(estrParams); } RETiRet; }