shared code
This commit is contained in:
parent
9f94b9ed20
commit
fbcbfbf7e5
@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2004 J.F.Dockes
|
/* Copyright (C) 2004-2018 J.F.Dockes
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation; either version 2 of the License, or
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
@ -14,7 +14,6 @@
|
|||||||
* Free Software Foundation, Inc.,
|
* Free Software Foundation, Inc.,
|
||||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
*/
|
*/
|
||||||
#ifndef TEST_EXECMD
|
|
||||||
#ifdef BUILDING_RECOLL
|
#ifdef BUILDING_RECOLL
|
||||||
#include "autoconfig.h"
|
#include "autoconfig.h"
|
||||||
#else
|
#else
|
||||||
@ -74,6 +73,7 @@ public:
|
|||||||
ExecCmdProvide *m_provide{0};
|
ExecCmdProvide *m_provide{0};
|
||||||
bool m_killRequest{false};
|
bool m_killRequest{false};
|
||||||
int m_timeoutMs{1000};
|
int m_timeoutMs{1000};
|
||||||
|
int m_killTimeoutMs{2000};
|
||||||
int m_rlimit_as_mbytes{0};
|
int m_rlimit_as_mbytes{0};
|
||||||
string m_stderrFile;
|
string m_stderrFile;
|
||||||
// Pipe for data going to the command
|
// Pipe for data going to the command
|
||||||
@ -122,6 +122,10 @@ void ExecCmd::setTimeout(int mS)
|
|||||||
m->m_timeoutMs = mS;
|
m->m_timeoutMs = mS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
void ExecCmd::setKillTimeout(int mS)
|
||||||
|
{
|
||||||
|
m->m_killTimeoutMs = mS;
|
||||||
|
}
|
||||||
void ExecCmd::setStderr(const std::string& stderrFile)
|
void ExecCmd::setStderr(const std::string& stderrFile)
|
||||||
{
|
{
|
||||||
m->m_stderrFile = stderrFile;
|
m->m_stderrFile = stderrFile;
|
||||||
@ -276,17 +280,23 @@ public:
|
|||||||
", SIGTERM)\n");
|
", SIGTERM)\n");
|
||||||
int ret = killpg(grp, SIGTERM);
|
int ret = killpg(grp, SIGTERM);
|
||||||
if (ret == 0) {
|
if (ret == 0) {
|
||||||
for (int i = 0; i < 3; i++) {
|
int ms_slept{0};
|
||||||
msleep(i == 0 ? 5 : (i == 1 ? 100 : 2000));
|
for (int i = 0; ; i++) {
|
||||||
|
int tosleep = i == 0 ? 5 : (i == 1 ? 100 : 1000);
|
||||||
|
msleep(tosleep);
|
||||||
|
ms_slept += tosleep;
|
||||||
int status;
|
int status;
|
||||||
(void)waitpid(m_parent->m_pid, &status, WNOHANG);
|
(void)waitpid(m_parent->m_pid, &status, WNOHANG);
|
||||||
if (kill(m_parent->m_pid, 0) != 0) {
|
if (kill(m_parent->m_pid, 0) != 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (i == 2) {
|
// killtimeout == -1 -> never KILL
|
||||||
LOGDEB("ExecCmd: killpg(" << (grp) << ", SIGKILL)\n");
|
if (m_parent->m_killTimeoutMs >= 0 &&
|
||||||
|
ms_slept >= m_parent->m_killTimeoutMs) {
|
||||||
|
LOGDEB("ExecCmd: killpg(" << grp << ", SIGKILL)\n");
|
||||||
killpg(grp, SIGKILL);
|
killpg(grp, SIGKILL);
|
||||||
(void)waitpid(m_parent->m_pid, &status, WNOHANG);
|
(void)waitpid(m_parent->m_pid, &status, WNOHANG);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1144,375 +1154,3 @@ void ReExec::reexec()
|
|||||||
argv[i] = 0;
|
argv[i] = 0;
|
||||||
execvp(m_argv[0].c_str(), (char *const*)argv);
|
execvp(m_argv[0].c_str(), (char *const*)argv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////
|
|
||||||
#else // TEST
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <signal.h>
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <iostream>
|
|
||||||
#include <sstream>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "log.h"
|
|
||||||
|
|
||||||
#include "execmd.h"
|
|
||||||
#ifdef BUILDING_RECOLL
|
|
||||||
#include "smallut.h"
|
|
||||||
#include "cancelcheck.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
#ifdef BUILDING_RECOLL
|
|
||||||
// Testing the rclexecm protocol outside of recoll. Here we use the
|
|
||||||
// rcldoc.py filter, you can try with rclaudio too, adjust the file arg
|
|
||||||
// accordingly
|
|
||||||
bool exercise_mhexecm(const string& cmdstr, const string& mimetype,
|
|
||||||
vector<string>& files)
|
|
||||||
{
|
|
||||||
ExecCmd cmd;
|
|
||||||
|
|
||||||
vector<string> myparams;
|
|
||||||
|
|
||||||
if (cmd.startExec(cmdstr, myparams, 1, 1) < 0) {
|
|
||||||
cerr << "startExec " << cmdstr << " failed. Missing command?\n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (vector<string>::const_iterator it = files.begin();
|
|
||||||
it != files.end(); it++) {
|
|
||||||
// Build request message
|
|
||||||
ostringstream obuf;
|
|
||||||
obuf << "Filename: " << (*it).length() << "\n" << (*it);
|
|
||||||
obuf << "Mimetype: " << mimetype.length() << "\n" << mimetype;
|
|
||||||
// Bogus parameter should be skipped by filter
|
|
||||||
obuf << "BogusParam: " << string("bogus").length() << "\n" << "bogus";
|
|
||||||
obuf << "\n";
|
|
||||||
cerr << "SENDING: [" << obuf.str() << "]\n";
|
|
||||||
// Send it
|
|
||||||
if (cmd.send(obuf.str()) < 0) {
|
|
||||||
// The real code calls zapchild here, but we don't need it as
|
|
||||||
// this will be handled by ~ExecCmd
|
|
||||||
//cmd.zapChild();
|
|
||||||
cerr << "send error\n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read answer
|
|
||||||
for (int loop = 0;; loop++) {
|
|
||||||
string name, data;
|
|
||||||
|
|
||||||
// Code from mh_execm.cpp: readDataElement
|
|
||||||
string ibuf;
|
|
||||||
// Read name and length
|
|
||||||
if (cmd.getline(ibuf) <= 0) {
|
|
||||||
cerr << "getline error\n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Empty line (end of message)
|
|
||||||
if (!ibuf.compare("\n")) {
|
|
||||||
cerr << "Got empty line\n";
|
|
||||||
name.clear();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filters will sometimes abort before entering the real
|
|
||||||
// protocol, ie if a module can't be loaded. Check the
|
|
||||||
// special filter error first word:
|
|
||||||
if (ibuf.find("RECFILTERROR ") == 0) {
|
|
||||||
cerr << "Got RECFILTERROR\n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We're expecting something like Name: len\n
|
|
||||||
vector<string> tokens;
|
|
||||||
stringToTokens(ibuf, tokens);
|
|
||||||
if (tokens.size() != 2) {
|
|
||||||
cerr << "bad line in filter output: [" << ibuf << "]\n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
vector<string>::iterator it = tokens.begin();
|
|
||||||
name = *it++;
|
|
||||||
string& slen = *it;
|
|
||||||
int len;
|
|
||||||
if (sscanf(slen.c_str(), "%d", &len) != 1) {
|
|
||||||
cerr << "bad line in filter output (no len): [" <<
|
|
||||||
ibuf << "]\n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Read element data
|
|
||||||
data.erase();
|
|
||||||
if (len > 0 && cmd.receive(data, len) != len) {
|
|
||||||
cerr << "MHExecMultiple: expected " << len <<
|
|
||||||
" bytes of data, got " << data.length() << endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty element: end of message
|
|
||||||
if (name.empty()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
cerr << "Got name: [" << name << "] data [" << data << "]\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static char *thisprog;
|
|
||||||
static char usage [] =
|
|
||||||
"trexecmd [-c -r -i -o] cmd [arg1 arg2 ...]\n"
|
|
||||||
" -c : test cancellation (ie: trexecmd -c sleep 1000)\n"
|
|
||||||
" -r : run reexec. Must be separate option.\n"
|
|
||||||
" -i : command takes input\n"
|
|
||||||
" -o : command produces output\n"
|
|
||||||
" If -i is set, we send /etc/group contents to whatever command is run\n"
|
|
||||||
" If -o is set, we print whatever comes out\n"
|
|
||||||
"trexecmd -m <filter> <mimetype> <file> [file ...]: test execm:\n"
|
|
||||||
" <filter> should be the path to an execm filter\n"
|
|
||||||
" <mimetype> the type of the file parameters\n"
|
|
||||||
"trexecmd -w cmd : do the 'which' thing\n"
|
|
||||||
"trexecmd -l cmd test getline\n"
|
|
||||||
;
|
|
||||||
|
|
||||||
static void Usage(void)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "%s: usage:\n%s", thisprog, usage);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int op_flags;
|
|
||||||
#define OPT_MOINS 0x1
|
|
||||||
#define OPT_i 0x4
|
|
||||||
#define OPT_w 0x8
|
|
||||||
#define OPT_c 0x10
|
|
||||||
#define OPT_r 0x20
|
|
||||||
#define OPT_m 0x40
|
|
||||||
#define OPT_o 0x80
|
|
||||||
#define OPT_l 0x100
|
|
||||||
|
|
||||||
// Data sink for data coming out of the command. We also use it to set
|
|
||||||
// a cancellation after a moment.
|
|
||||||
class MEAdv : public ExecCmdAdvise {
|
|
||||||
public:
|
|
||||||
void newData(int cnt) {
|
|
||||||
if (op_flags & OPT_c) {
|
|
||||||
#ifdef BUILDING_RECOLL
|
|
||||||
static int callcnt;
|
|
||||||
if (callcnt++ == 10) {
|
|
||||||
// Just sets the cancellation flag
|
|
||||||
CancelCheck::instance().setCancel();
|
|
||||||
// Would be called from somewhere else and throws an
|
|
||||||
// exception. We call it here for simplicity
|
|
||||||
CancelCheck::instance().checkCancel();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
cerr << "newData(" << cnt << ")" << endl;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Data provider, used if the -i flag is set
|
|
||||||
class MEPv : public ExecCmdProvide {
|
|
||||||
public:
|
|
||||||
FILE *m_fp;
|
|
||||||
string *m_input;
|
|
||||||
MEPv(string *i)
|
|
||||||
: m_input(i) {
|
|
||||||
m_fp = fopen("/etc/group", "r");
|
|
||||||
}
|
|
||||||
~MEPv() {
|
|
||||||
if (m_fp) {
|
|
||||||
fclose(m_fp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void newData() {
|
|
||||||
char line[1024];
|
|
||||||
if (m_fp && fgets(line, 1024, m_fp)) {
|
|
||||||
m_input->assign((const char *)line);
|
|
||||||
} else {
|
|
||||||
m_input->erase();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ReExec reexec;
|
|
||||||
int main(int argc, char *argv[])
|
|
||||||
{
|
|
||||||
reexec.init(argc, argv);
|
|
||||||
|
|
||||||
if (0) {
|
|
||||||
// Disabled: For testing reexec arg handling
|
|
||||||
vector<string> newargs;
|
|
||||||
newargs.push_back("newarg");
|
|
||||||
newargs.push_back("newarg1");
|
|
||||||
newargs.push_back("newarg2");
|
|
||||||
newargs.push_back("newarg3");
|
|
||||||
newargs.push_back("newarg4");
|
|
||||||
reexec.insertArgs(newargs, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
thisprog = argv[0];
|
|
||||||
argc--;
|
|
||||||
argv++;
|
|
||||||
|
|
||||||
while (argc > 0 && **argv == '-') {
|
|
||||||
(*argv)++;
|
|
||||||
if (!(**argv))
|
|
||||||
/* Cas du "adb - core" */
|
|
||||||
{
|
|
||||||
Usage();
|
|
||||||
}
|
|
||||||
while (**argv)
|
|
||||||
switch (*(*argv)++) {
|
|
||||||
case 'c':
|
|
||||||
op_flags |= OPT_c;
|
|
||||||
break;
|
|
||||||
case 'r':
|
|
||||||
op_flags |= OPT_r;
|
|
||||||
break;
|
|
||||||
case 'w':
|
|
||||||
op_flags |= OPT_w;
|
|
||||||
break;
|
|
||||||
#ifdef BUILDING_RECOLL
|
|
||||||
case 'm':
|
|
||||||
op_flags |= OPT_m;
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
case 'i':
|
|
||||||
op_flags |= OPT_i;
|
|
||||||
break;
|
|
||||||
case 'l':
|
|
||||||
op_flags |= OPT_l;
|
|
||||||
break;
|
|
||||||
case 'o':
|
|
||||||
op_flags |= OPT_o;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Usage();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
argc--;
|
|
||||||
argv++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argc < 1) {
|
|
||||||
Usage();
|
|
||||||
}
|
|
||||||
|
|
||||||
string arg1 = *argv++;
|
|
||||||
argc--;
|
|
||||||
vector<string> l;
|
|
||||||
while (argc > 0) {
|
|
||||||
l.push_back(*argv++);
|
|
||||||
argc--;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef BUILDING_RECOLL
|
|
||||||
DebugLog::getdbl()->setloglevel(DEBDEB1);
|
|
||||||
DebugLog::setfilename("stderr");
|
|
||||||
#endif
|
|
||||||
signal(SIGPIPE, SIG_IGN);
|
|
||||||
|
|
||||||
if (op_flags & OPT_r) {
|
|
||||||
// Test reexec. Normally only once, next time we fall through
|
|
||||||
// because we remove the -r option (only works if it was isolated, not like -rc
|
|
||||||
chdir("/");
|
|
||||||
argv[0] = strdup("");
|
|
||||||
sleep(1);
|
|
||||||
cerr << "Calling reexec\n";
|
|
||||||
// We remove the -r arg from list, otherwise we are going to
|
|
||||||
// loop (which you can try by commenting out the following
|
|
||||||
// line)
|
|
||||||
reexec.removeArg("-r");
|
|
||||||
reexec.reexec();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (op_flags & OPT_w) {
|
|
||||||
// Test "which" method
|
|
||||||
string path;
|
|
||||||
if (ExecCmd::which(arg1, path)) {
|
|
||||||
cout << path << endl;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
#ifdef BUILDING_RECOLL
|
|
||||||
} else if (op_flags & OPT_m) {
|
|
||||||
if (l.size() < 2) {
|
|
||||||
Usage();
|
|
||||||
}
|
|
||||||
string mimetype = l[0];
|
|
||||||
l.erase(l.begin());
|
|
||||||
return exercise_mhexecm(arg1, mimetype, l) ? 0 : 1;
|
|
||||||
#endif
|
|
||||||
} else if (op_flags & OPT_l) {
|
|
||||||
ExecCmd mexec;
|
|
||||||
|
|
||||||
if (mexec.startExec(arg1, l, false, true) < 0) {
|
|
||||||
cerr << "Startexec failed\n";
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
string output;
|
|
||||||
int ret = mexec.getline(output, 2);
|
|
||||||
cerr << "Got ret " << ret << " output " << output << endl;
|
|
||||||
cerr << "Waiting\n";
|
|
||||||
int status = mexec.wait();
|
|
||||||
cerr << "Got status " << status << endl;
|
|
||||||
exit(status);
|
|
||||||
} else {
|
|
||||||
// Default: execute command line arguments
|
|
||||||
ExecCmd mexec;
|
|
||||||
|
|
||||||
// Set callback to be called whenever there is new data
|
|
||||||
// available and at a periodic interval, to check for
|
|
||||||
// cancellation
|
|
||||||
MEAdv adv;
|
|
||||||
mexec.setAdvise(&adv);
|
|
||||||
mexec.setTimeout(5);
|
|
||||||
|
|
||||||
// Stderr output goes there
|
|
||||||
mexec.setStderr("/tmp/trexecStderr");
|
|
||||||
|
|
||||||
// A few environment variables. Check with trexecmd env
|
|
||||||
mexec.putenv("TESTVARIABLE1=TESTVALUE1");
|
|
||||||
mexec.putenv("TESTVARIABLE2=TESTVALUE2");
|
|
||||||
mexec.putenv("TESTVARIABLE3=TESTVALUE3");
|
|
||||||
|
|
||||||
string input, output;
|
|
||||||
MEPv pv(&input);
|
|
||||||
|
|
||||||
string *ip = 0;
|
|
||||||
if (op_flags & OPT_i) {
|
|
||||||
ip = &input;
|
|
||||||
mexec.setProvide(&pv);
|
|
||||||
}
|
|
||||||
string *op = 0;
|
|
||||||
if (op_flags & OPT_o) {
|
|
||||||
op = &output;
|
|
||||||
}
|
|
||||||
|
|
||||||
int status = -1;
|
|
||||||
try {
|
|
||||||
status = mexec.doexec(arg1, l, ip, op);
|
|
||||||
} catch (...) {
|
|
||||||
cerr << "CANCELLED" << endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(stderr, "Status: 0x%x\n", status);
|
|
||||||
if (op_flags & OPT_o) {
|
|
||||||
cout << output;
|
|
||||||
}
|
|
||||||
exit(status >> 8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif // TEST
|
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2004 J.F.Dockes
|
/* Copyright (C) 2004-2018 J.F.Dockes
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation; either version 2 of the License, or
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
@ -117,6 +117,17 @@ public:
|
|||||||
*/
|
*/
|
||||||
void setStderr(const std::string& stderrFile);
|
void setStderr(const std::string& stderrFile);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set kill wait timeout. This is the maximum time we'll wait for
|
||||||
|
* the command after sending a SIGTERM, before sending a SIGKILL.
|
||||||
|
|
||||||
|
* @param mS the maximum number of mS to wait. Note that values
|
||||||
|
* below 1000 mS make no sense as the program will sleep for
|
||||||
|
* longer time before retrying the waitpid(). Use -1 for
|
||||||
|
* forever (bad idea), 0 for absolutely no pity.
|
||||||
|
*/
|
||||||
|
void setKillTimeout(int mS);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute command.
|
* Execute command.
|
||||||
*
|
*
|
||||||
|
|||||||
@ -34,6 +34,8 @@
|
|||||||
#ifndef _LOG_H_X_INCLUDED_
|
#ifndef _LOG_H_X_INCLUDED_
|
||||||
#define _LOG_H_X_INCLUDED_
|
#define _LOG_H_X_INCLUDED_
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
@ -191,4 +193,17 @@ private:
|
|||||||
#endif
|
#endif
|
||||||
#define LOGFATAL LOGFAT
|
#define LOGFATAL LOGFAT
|
||||||
|
|
||||||
|
#if (_POSIX_C_SOURCE >= 200112L) && ! _GNU_SOURCE
|
||||||
|
#define LOGSYSERR(who, what, arg) { \
|
||||||
|
char buf[200]; buf[0] = 0; strerror_r(errno, buf, 200); \
|
||||||
|
LOGERR(who << ": " << what << "(" << arg << "): errno " << errno << \
|
||||||
|
": " << buf << std::endl); \
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#define LOGSYSERR(who, what, arg) { \
|
||||||
|
char buf[200]; buf[0] = 0; \
|
||||||
|
LOGERR(who << ": " << what << "(" << arg << "): errno " << errno << \
|
||||||
|
": " << strerror_r(errno, buf, 200) << std::endl); \
|
||||||
|
}
|
||||||
|
#endif
|
||||||
#endif /* _LOG_H_X_INCLUDED_ */
|
#endif /* _LOG_H_X_INCLUDED_ */
|
||||||
|
|||||||
@ -72,10 +72,11 @@ using namespace std;
|
|||||||
static const int one = 1;
|
static const int one = 1;
|
||||||
static const int zero = 0;
|
static const int zero = 0;
|
||||||
|
|
||||||
|
#ifndef LOGSYSERR
|
||||||
#define LOGSYSERR(who, call, spar) \
|
#define LOGSYSERR(who, call, spar) \
|
||||||
LOGERR(who << ": " << call << "(" << spar << ") errno " << \
|
LOGERR(who << ": " << call << "(" << spar << ") errno " << \
|
||||||
errno << " (" << strerror(errno) << ")\n")
|
errno << " (" << strerror(errno) << ")\n")
|
||||||
|
#endif
|
||||||
#ifndef MIN
|
#ifndef MIN
|
||||||
#define MIN(a,b) ((a)<(b)?(a):(b))
|
#define MIN(a,b) ((a)<(b)?(a):(b))
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user