/* -*- c++ -*- Copyright (C) 2004-2013 Christian Wieninger 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 Or, point your browser to http://www.gnu.org/licenses/old-licenses/gpl-2.0.html The author can be reached at cwieninger@gmx.de The project's page is at http://winni.vdr-developer.org/epgsearch */ #include #include #include #include #include #include #include #ifdef __FreeBSD__ #include #endif #include "uservars.h" #include "epgsearchtools.h" #include "epgsearchext.h" #include "epgsearchcats.h" #include "epgsearchcfg.h" #include "svdrpclient.h" #include "distance.h" #include "md5.h" #include "afuzzy.h" #include "timerstatus.h" #include #ifdef HAVE_PCREPOSIX #include #elif defined(HAVE_LIBTRE) #include #else #include #endif const char AllowedChars[] = trNOOP("$ abcdefghijklmnopqrstuvwxyz0123456789-.,#~\\^$[]|()*+?{}/:%@&_"); extern bool isUTF8; int CompareEventTime(const void *p1, const void *p2) { time_t time1 = (*(cSearchResult **)p1)->event->StartTime(); time_t time2 = (*(cSearchResult **)p2)->event->StartTime(); if (time1 == time2) return (int)(ChannelNrFromEvent((*(cSearchResult **)p1)->event) - ChannelNrFromEvent((*(cSearchResult **)p2)->event)); else return (int)(time1 - time2); } int CompareEventChannel(const void *p1, const void *p2) { int ch1 = ChannelNrFromEvent((*(cSearchResult **)p1)->event); int ch2 = ChannelNrFromEvent((*(cSearchResult **)p2)->event); if (ch1 == ch2) return (int)((*(cSearchResult **)p1)->event->StartTime() - (*(cSearchResult **)p2)->event->StartTime()); else return ch1 - ch2; } int CompareSearchExtPrioDescTerm(const void *p1, const void *p2) { int prio1 = (*(cSearchExt **)p1)->Priority; int prio2 = (*(cSearchExt **)p2)->Priority; if (prio2 != prio1) return prio2 - prio1; else return strcmp((*(cSearchExt **)p1)->search, (*(cSearchExt **)p2)->search); } cString IndentMenuItem(const char* szString, int indentions) { char* szIndented = NULL; msprintf(&szIndented, "%*s", strlen(szString)+indentions*2, szString); cString szIndentedStr(szIndented, true /*take pointer*/); return szIndentedStr; } bool MatchesSearchMode(const char* szTest, const char* searchText, int mode, const char* delim, int tolerance) { if (szTest && *szTest) { if (mode == 0) // substring return (strstr(szTest, searchText) != NULL); else if (mode == 1 || mode == 2) // AND or OR { bool bTesting = false; char *pstrSearchToken, *pptr; bool bFirst=true; char *pstrSearch=strdup(searchText); pstrSearchToken=strtok_r(pstrSearch, delim, &pptr); while(pstrSearchToken) { if(szTest && strstr(szTest, skipspace(pstrSearchToken))) { if(mode==1) { // means AND if(bFirst) { bTesting=true; bFirst=false; } else bTesting&=true; } else bTesting|=true; } else {// not found!! if(mode==1) { // means AND bTesting=false; bFirst=false; } } pstrSearchToken=strtok_r(NULL, delim, &pptr); } free(pstrSearch); return bTesting; } else if (mode == 3) // match exactly { if (strcmp(szTest, searchText) == 0) return true; else return false; } else if (mode == 4) // regexp { regex_t re; if ( 0 == regcomp(&re, searchText, REG_EXTENDED | REG_NOSUB) ) { int status = regexec( &re, szTest, 0, NULL, 0); regfree(&re); return (status == 0); } return false; } else if (mode == 5) // fuzzy { AFUZZY af = { NULL, NULL, NULL, NULL, NULL, NULL, { 0 }, { 0 }, 0, 0, 0, 0, 0, 0 }; string query = searchText?searchText:""; if (query.size() > 32) query = query.substr(0, 32); afuzzy_init(query.c_str(), tolerance, 0, &af); /* Checking substring */ int res = afuzzy_checkSUB(szTest, &af); afuzzy_free(&af); return (res > 0); } else if (mode >= 10 && mode <= 15) { int testvalue = atoi(szTest); int value = atoi(searchText); if (value == 0) return true; if (mode == 10) // less return testvalue < value; else if (mode == 11) // less or equal return testvalue <= value; else if (mode == 12) // greater return testvalue > value; else if (mode == 13) // greater or equal return testvalue >= value; else if (mode == 14) // equal return testvalue == value; else if (mode == 15) // not equal return testvalue != value; } } return false; } void ToLower(char* szText) { if (!szText) return; if (!isUTF8) { for (int loop = 0; szText[loop] !=0; loop++) szText[loop] = tolower(szText[loop]); return; } else { int length = strlen(szText)+1; uint* valueUtf8 = new uint[length]; int lengthUtf8 = Utf8ToArray(szText, valueUtf8, length); for(int i=0; iDescription(), SearchExtCat->name, SearchExtCat->format); } char* GetExtEPGValue(const char* description, const char* catname, const char *format) { if (isempty(description)) return NULL; char* tmp1 = NULL; char* tmp2 = NULL; // search the category, must be the first line or at the beginning of a line if (msprintf(&tmp1, "\n%s: ", catname)==-1 || msprintf(&tmp2, "%s: ", catname)==-1) return NULL; char* descr = strdup(description); char* cat = NULL; int valueOffset = 0; if ((cat = strstr(descr, tmp1)) != NULL) { cat++; // skip linefeed valueOffset = strlen(tmp1); } else if (strstr(descr, tmp2) == descr) // in first line { cat = descr; valueOffset = strlen(tmp2)+1; } else { free(descr); free(tmp1); free(tmp2); return NULL; } // search the value to appear before the next line feed or end char* end = strchr(cat, '\n'); int endpos = strlen(cat); if (end) endpos = end - cat; cat[endpos] = 0; char* value = NULL; msprintf(&value, "%s", cat + valueOffset - 1); if (format) { int ivalue; if (sscanf(value,"%8i",&ivalue)==1) { free(value); value = NULL; msprintf(&value, format, ivalue); } } free(descr); free(tmp1); free(tmp2); return value; } char* GetAuxValue(const char* aux, const char* name) { if (isempty(aux)) return NULL; char* descr = strdup(aux); char* beginaux = strstr(descr, ""); char* endaux = strstr(descr, ""); if (!beginaux || !endaux) { free(descr); return NULL; } beginaux += 11; // strlen(""); endaux[0] = 0; memmove(descr, beginaux, endaux - beginaux + 1); if (strcmp(name, "epgsearch") == 0) return descr; // full aux int namelen = strlen(name); char catname[100] = ""; catname[0] = '<'; memcpy(catname + 1, name, namelen); catname[1 + namelen] = '>'; catname[2 + namelen] = 0; char* cat = strcasestr(descr, catname); if (!cat) { free(descr); return NULL; } cat += namelen + 2; char* end = strstr(cat, "Info()) return NULL; return GetAuxValue(recording->Info()->Aux(), name); } char* GetAuxValue(const cTimer *timer, const char* name) { if (!timer || !timer->Aux()) return NULL; return GetAuxValue(timer->Aux(), name); } string UpdateAuxValue(string aux, string section, long num) { return UpdateAuxValue(aux, section, NumToString(num)); } string UpdateAuxValue(string aux, string section, string value) { string secStart = "<" + section + ">"; string secEnd = ""; int valueStartPos = aux.find(secStart); int valueEndPos = aux.find(secEnd); if (valueStartPos >= 0 && valueEndPos >= 0) aux.replace(valueStartPos + secStart.size(), valueEndPos - valueStartPos - secStart.size(), value); else aux += secStart + value + secEnd; return aux; } // replace s1 with s2 in s ignoring the case of s1 char *strreplacei(char *s, const char *s1, const char *s2) { char *p = strcasestr(s, s1); if (p) { int of = p - s; int l = strlen(s); int l1 = strlen(s1); int l2 = 0; if (s2) l2 = strlen(s2); if (l2 > l1) s = (char *)realloc(s, l + l2 - l1 + 1); if (l2 != l1) memmove(s + of + l2, s + of + l1, l - of - l1 + 1); memcpy(s + of, s2, l2); } return s; } std::string strreplace( std::string& result, const std::string& replaceWhat, const std::string& replaceWithWhat) { while(1) { const int pos = result.find(replaceWhat); if (pos==-1) break; result.replace(pos,replaceWhat.size(),replaceWithWhat); } return result; } void sleepMSec(long ms) { cCondWait::SleepMs(ms); } void sleepSec(long s) { sleepMSec(s * 1000); } bool SendViaSVDRP(cString SVDRPcmd) { bool bSuccess = true; cString cmdbuf; if (EPGSearchConfig.useExternalSVDRP) { cmdbuf = cString::sprintf("%s -p %d \"%s\"", epgsSVDRP::cSVDRPClient::SVDRPSendCmd, EPGSearchConfig.SVDRPPort, *SVDRPcmd); FILE *p = popen(cmdbuf, "r"); if (p) pclose(p); else { LogFile.eSysLog("can't open pipe for command '%s'", *cmdbuf); bSuccess = false; } } else { cmdbuf = SVDRPcmd; epgsSVDRP::cSVDRPClient client; if (!client.SendCmd(*cmdbuf)) { LogFile.eSysLog("command '%s' failed", *cmdbuf); bSuccess = false; } } return bSuccess; } int SendMsg(cString Message, bool confirm, int seconds, eMessageType messageType) { int Keys = Skins.QueueMessage(messageType, Message, seconds, confirm?seconds+2:0); return Keys; } bool InEditMode(const char* ItemText, const char* ItemName, const char* ItemValue) { bool bEditMode = true; // ugly solution to detect, if in edit mode char* value = strdup(ItemText); strreplace(value, ItemName, ""); strreplace(value, ":\t", ""); // for bigpatch strreplace(value, "\t", ""); if (strlen(value) == strlen(ItemValue)) bEditMode = false; free(value); return bEditMode; } // checks if the timer was triggered from a search timer and return a pointer to the search cSearchExt* TriggeredFromSearchTimer(const cTimer* timer) { char* searchID = GetAuxValue(timer, "s-id"); if (!searchID) return NULL; cSearchExt* search = SearchExts.GetSearchFromID(atoi(searchID)); free(searchID); return search; } int TriggeredFromSearchTimerID(const cTimer* timer) { cSearchExt* trigger = TriggeredFromSearchTimer(timer); if (trigger) return trigger->ID; else return -1; } double FuzzyMatch(const char* s1, const char* s2, int maxLength) { Distance D; int dist = D.LD (s1, s2, maxLength); double fMaxLength = std::max(strlen(s1), strlen(s2)); return (fMaxLength - dist)/fMaxLength; } bool DescriptionMatches(const char* eDescr, const char* rDescr, int matchLimit) { if (eDescr == NULL && rDescr == NULL) return true; if (eDescr == NULL && rDescr != NULL) return false; if (eDescr != NULL && rDescr == NULL) return false; int l_eDescr = strlen(eDescr); int l_rDescr = strlen(rDescr); if (l_eDescr == l_rDescr && strcmp(eDescr, rDescr) == 0) return true; // partial match: // first check the length, should only be different at match limit int minLength = std::min(l_eDescr, l_rDescr); int maxLength = std::max(l_eDescr, l_rDescr); if (100*double(minLength)/double(maxLength) < matchLimit) return false; // last try with Levenshtein Distance, only compare the first 1000 chars double fMatch = FuzzyMatch(eDescr, rDescr, 1000); double tmp_matchlimit = matchLimit/100.0; if(maxLength - minLength < 5 && matchLimit < 95) { tmp_matchlimit = 0.95; LogFile.Log(2,"difference between both descriptions is < 5 setting matchlimit to: %.2f %%", tmp_matchlimit*100); } if (fMatch > tmp_matchlimit) { LogFile.Log(2,"match is: %.2f %%", fMatch*100); return true; } return false; } const cEvent* GetEvent(const cTimer* timer) { const cEvent* event = NULL; const cChannel *channel = timer->Channel(); time_t Time = timer->StartTime() + (timer->StopTime() - timer->StartTime()) / 2; for (int seconds = 0; seconds <= 3; seconds++) { { LOCK_SCHEDULES_READ; if (Schedules) { const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID()); if (Schedule) { event = Schedule->GetEventAround(Time); if (event) return event; } } } if (seconds == 0) LogFile.Log(2,"waiting for EPG info..."); sleepSec(1); } LogFile.Log(1,"no EPG info available"); return NULL; } // this extracts the real description from a given epg entry cutting all that looks like a category line // we assume that a category has a name not longer than MAXCATNAMELENGTH and a value not longer than // MAXCATVALUELENGTH (so in most cases e.g. the 'cast' category will stay part of the description). name and // value are separated with ': ' #define MAXCATNAMELENGTH 40 #define MAXCATVALUELENGTH 60 char* GetRawDescription(const char* descr) { if (!descr || !*descr) return NULL; char* rawDescr = (char*) calloc(strlen(descr)+1, sizeof(char)); const char* tmp = descr; while(tmp) { // extract a single line const char* lf = strchr(tmp, '\n'); char* line = NULL; if (lf) line = strndup(tmp, lf-tmp); else line = strdup(tmp); // if (lf) *lf = 0; // check for category char* delim = strstr(line, ": "); if (!delim || (delim && (delim - line > MAXCATNAMELENGTH || strlen(line) - (delim - line) + 2 > MAXCATVALUELENGTH))) if (*line) strcat(rawDescr, line); if (lf) tmp += strlen(line)+1; else tmp = NULL; free(line); } return rawDescr; } void PrepareTimerFile(const cEvent* event, cTimer* timer) { if (!event) return; if (EPGSearchConfig.addSubtitleToTimer == addSubtitleNever && strlen(EPGSearchConfig.defrecdir) == 0) // nothing to do return; if (!isempty(event->ShortText())) // add subtitle if present { bool addSubtitle = (EPGSearchConfig.addSubtitleToTimer != addSubtitleNever); if (EPGSearchConfig.addSubtitleToTimer == addSubtitleSmart) if (event->Duration() > 80*60) addSubtitle = false; if (addSubtitle) { char tmp[MaxFileName] = ""; snprintf(tmp, MaxFileName, "%s~%s", event->Title(), event->ShortText()); timer->SetFile(tmp); } } if (strlen(EPGSearchConfig.defrecdir) > 0) { char directory[MaxFileName] = ""; strn0cpy(directory, EPGSearchConfig.defrecdir,sizeof(directory)); cVarExpr varExprDir(directory); if (!varExprDir.DependsOnVar("%title%", event)) { strcat(directory, "~"); strcat(directory, timer->File()); } // parse the epxression and evaluate it cVarExpr varExpr(directory); strcpy(directory, varExpr.Evaluate(event).c_str()); if (strchr(directory, '%') == NULL) // only set directory to new value if all categories could have been replaced timer->SetFile(directory); } } bool EventsMatch(const cEvent* event1, const cEvent* event2, bool compareTitle, int compareSubtitle, bool compareSummary, int compareDate, unsigned long catvaluesAvoidRepeat, int matchLimit) { if (!event1 || !event2) return false; if (event1 == event2) return true; // only compare the alphanumeric portions string Title1 = ""; string Title2 = ""; if (compareTitle) { string s1 = event1->Title()?event1->Title():""; string s2 = event2->Title()?event2->Title():""; Title1 = GetAlNum(s1); Title2 = GetAlNum(s2); std::transform(Title1.begin(), Title1.end(), Title1.begin(), tolower); std::transform(Title2.begin(), Title2.end(), Title2.begin(), tolower); } string Subtitle1 = ""; string Subtitle2 = ""; if (compareSubtitle) { string s1 = event1->ShortText()?event1->ShortText():""; string s2 = event2->ShortText()?event2->ShortText():""; Subtitle1 = GetAlNum(s1); Subtitle2 = GetAlNum(s2); std::transform(Subtitle1.begin(), Subtitle1.end(), Subtitle1.begin(), tolower); std::transform(Subtitle2.begin(), Subtitle2.end(), Subtitle2.begin(), tolower); } string compareExpression = ""; if (compareDate == 1) compareExpression = "%date%"; if (compareDate == 2) compareExpression = "%year%-%week%"; if (compareDate == 3) compareExpression = "%year%-%month%"; bool match = false; if ((!compareTitle || Title1 == Title2) && (!compareSubtitle || (Subtitle1 == Subtitle2 && Subtitle1!=""))) { const char* Descr1 = event1->Description(); const char* Descr2 = event2->Description(); if (compareSummary) { char* rawDescr1 = GetRawDescription(Descr1); char* rawDescr2 = GetRawDescription(Descr2); match = DescriptionMatches(rawDescr1, rawDescr2, matchLimit); free(rawDescr1); free(rawDescr2); if (!match) return false; } if (compareExpression.size() > 0) { cVarExpr varExpr(compareExpression); string resEvent1 = varExpr.Evaluate(event1); string resEvent2 = varExpr.Evaluate(event2); if (resEvent1 != resEvent2) return false; } if (catvaluesAvoidRepeat != 0) // check categories { bool bCatMatch = ((Descr1 && Descr2) || (!Descr1 && !Descr2)); cSearchExtCat *SearchExtCat = SearchExtCats.First(); int index = 0; while (catvaluesAvoidRepeat > 0 && SearchExtCat && bCatMatch) { if (catvaluesAvoidRepeat & (1<name, SearchExtCat->format); char* CatValue2 = GetExtEPGValue(Descr2, SearchExtCat->name, SearchExtCat->format); if ((!CatValue1 && CatValue2) || (!CatValue2 && CatValue1) || (CatValue1 && CatValue2 && strcmp(CatValue1, CatValue2) != 0)) bCatMatch = false; free(CatValue1); free(CatValue2); } SearchExtCat = SearchExtCats.Next(SearchExtCat); index++; } if (bCatMatch) match = true; } else match = true; } return match; } int ChannelNrFromEvent(const cEvent* pEvent) { if (!pEvent) return -1; LOCK_CHANNELS_READ; const cChannel* channel = Channels->GetByChannelID(pEvent->ChannelID(), true, true); if (!channel) return -1; else return channel->Number(); } void DelTimer(int index) { cString cmdbuf = cString::sprintf("DELT %d", index); LogFile.Log(2, "delete timer %d", index); SendViaSVDRP(cmdbuf); gl_timerStatusMonitor->SetConflictCheckAdvised(); } char* FixSeparators(char* buffer, char sep) { int l = strlen(buffer); char *dest = buffer; for (int i = 0; i < l; i ++) { char c = buffer[i]; int j = i; if (c == sep) { for (j = i + 1; (j < l) & (buffer[j] == ' '); j++) ; if ((j <= l) | (i + 1 < j)) { switch (buffer[j]) { case '\t': i = j; c = '\t'; break; case 0: i = j; c = 0; break; default: break; } } } if (c == '\t') { for (; (j < l) & (buffer[j] == ' '); j++) ; if (j < l && buffer[j] == sep) { buffer[j] = '\t'; i = j - 1; continue; } } *dest++ = c; } *dest = 0; return buffer; } cString DateTime(time_t t) { char buffer[32]; if (t == 0) time(&t); struct tm tm_r; tm *tm = localtime_r(&t, &tm_r); snprintf(buffer, sizeof(buffer), "%02d.%02d. %02d:%02d", tm->tm_mday, tm->tm_mon + 1, tm->tm_hour, tm->tm_min); return buffer; } string NumToString(long num) { ostringstream os; os << num; return os.str(); } int FindIgnoreCase(const string& expr, const string& query) { const char *p = expr.c_str(); const char *r = strcasestr(p, query.c_str()); if (!r) return -1; return r - p; } bool EqualsNoCase(const string& a, const string& b) { return strcasecmp(a.c_str(), b.c_str()) == 0; } string Strip(const string& input) { string str = input; string::size_type pos = str.find_last_not_of(' '); if(pos != string::npos) { str.erase(pos + 1); pos = str.find_first_not_of(' '); if(pos != string::npos) str.erase(0, pos); } else str.erase(str.begin(), str.end()); return str; } string GetAlNum(const string& s) { string res; for(unsigned int i=0; i= 0) result.replace(pos, what.size(), with); return result; } string EscapeString(const string& S) { string tmp = S; int apostrophPos = 0; int apostrophTempPos = 0; while((apostrophPos = tmp.find("'", apostrophTempPos)) >= apostrophTempPos) { tmp.replace(apostrophPos, 1, "'\"'\"'"); apostrophTempPos = apostrophPos + 5; } return tmp; } string QuoteApostroph(const string& S) { string tmp = S; int apostrophPos = 0; int apostrophTempPos = 0; while((apostrophPos = tmp.find("\"", apostrophTempPos)) >= apostrophTempPos) { tmp.replace(apostrophPos, 1, "\\\""); apostrophTempPos = apostrophPos + 2; } return tmp; } string MD5(const string& input) { char* szInput = strdup(input.c_str()); if (!szInput) return ""; char* szRes = MD5String(szInput); string res = szRes; free(szRes); return res; } void SetAux(cTimer* timer, string aux) { if (!timer) return; string timerText = *timer->ToText(); string::size_type auxPos = string::npos; for(int i=0; i<=7; i++) // get aux value auxPos = timerText.find(":", auxPos == string::npos?0:auxPos+1); if (auxPos == string::npos) return; timerText.replace(auxPos+1, timerText.size()-auxPos+1, aux); timer->Parse(timerText.c_str()); } int msprintf(char **strp, const char *fmt, ...) { va_list ap; va_start (ap, fmt); int res=vasprintf (strp, fmt, ap); va_end (ap); return res; } std::string GetCodeset() { // Taken from VDR's vdr.c char *CodeSet = NULL; if (setlocale(LC_CTYPE, "")) CodeSet = nl_langinfo(CODESET); else { char *LangEnv = getenv("LANG"); // last resort in case locale stuff isn't installed if (LangEnv) { CodeSet = strchr(LangEnv, '.'); if (CodeSet) CodeSet++; // skip the dot } } if (CodeSet) return std::string(CodeSet); else return "ISO-8859-15"; } /* Read a line from a socket */ ssize_t Readline(int sockd, char *vptr, size_t maxlen) { size_t n, rc; char c, *buffer; buffer = vptr; for ( n = 1; n < maxlen; n++ ) { if ( (rc = read(sockd, &c, 1)) == 1 ) { if ( c == '\n' ) break; *buffer++ = c; } else if ( rc == 0 ) { if ( n == 1 ) return 0; else break; } else { if ( errno == EINTR ) continue; return -1; } } *buffer = 0; return n; } /* Write a line to a socket */ ssize_t Writeline(int sockd, const char *vptr, ssize_t n) { ssize_t nleft; ssize_t nwritten; const char *buffer; buffer = vptr; nleft = n; while ( nleft > 0 ) { if ( (nwritten = write(sockd, buffer, nleft)) <= 0 ) { if ( errno == EINTR ) nwritten = 0; else return -1; } nleft -= nwritten; buffer += nwritten; } return n; } long getAddrFromString(const char* hostnameOrIp, struct sockaddr_in* addr) { unsigned long ip; struct hostent * he; if(hostnameOrIp==NULL || addr==NULL) return -1; ip=inet_addr(hostnameOrIp); if(ip!=INADDR_NONE) { addr->sin_addr.s_addr=ip; return 0; } else { he=gethostbyname(hostnameOrIp); if(he==NULL) return -1; else memcpy(&(addr->sin_addr),he->h_addr_list[0],4); return 0; } } char *cCommand::result = NULL; cCommand::cCommand(void) { title = command = NULL; confirm = false; } cCommand::~cCommand() { free(title); free(command); } bool cCommand::Parse(const char *s) { const char *p = strchr(s, ':'); if (p) { int l = p - s; if (l > 0) { title = MALLOC(char, l + 1); stripspace(strn0cpy(title, s, l + 1)); if (!isempty(title)) { int l = strlen(title); if (l > 1 && title[l - 1] == '?') { confirm = true; title[l - 1] = 0; } command = stripspace(strdup(skipspace(p + 1))); return !isempty(command); } } } return false; } const char *cCommand::Execute(const char *Parameters) { free(result); result = NULL; cString cmdbuf; if (Parameters) cmdbuf = cString::sprintf("%s %s", command, Parameters); const char *cmd = *cmdbuf ? *cmdbuf : command; dsyslog("executing command '%s'", cmd); cPipe p; if (p.Open(cmd, "r")) { int l = 0; int c; while ((c = fgetc(p)) != EOF) { if (l % 20 == 0) result = (char *)realloc(result, l + 21); result[l++] = char(c); } if (result) result[l] = 0; p.Close(); } else esyslog("ERROR: can't open pipe for command '%s'", cmd); return result; }