a/src/utils/execmd.cpp b/src/utils/execmd.cpp
1
/* Copyright (C) 2004 J.F.Dockes
1
/* Copyright (C) 2004-2018 J.F.Dockes
2
 *   This program is free software; you can redistribute it and/or modify
2
 *   This program is free software; you can redistribute it and/or modify
3
 *   it under the terms of the GNU General Public License as published by
3
 *   it under the terms of the GNU General Public License as published by
4
 *   the Free Software Foundation; either version 2 of the License, or
4
 *   the Free Software Foundation; either version 2 of the License, or
5
 *   (at your option) any later version.
5
 *   (at your option) any later version.
6
 *
6
 *
...
...
12
 *   You should have received a copy of the GNU General Public License
12
 *   You should have received a copy of the GNU General Public License
13
 *   along with this program; if not, write to the
13
 *   along with this program; if not, write to the
14
 *   Free Software Foundation, Inc.,
14
 *   Free Software Foundation, Inc.,
15
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
15
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
16
 */
16
 */
17
#ifndef TEST_EXECMD
18
#ifdef BUILDING_RECOLL
17
#ifdef BUILDING_RECOLL
19
#include "autoconfig.h"
18
#include "autoconfig.h"
20
#else
19
#else
21
#include "config.h"
20
#include "config.h"
22
#endif
21
#endif
...
...
72
    vector<string>   m_env;
71
    vector<string>   m_env;
73
    ExecCmdAdvise   *m_advise{0};
72
    ExecCmdAdvise   *m_advise{0};
74
    ExecCmdProvide  *m_provide{0};
73
    ExecCmdProvide  *m_provide{0};
75
    bool             m_killRequest{false};
74
    bool             m_killRequest{false};
76
    int              m_timeoutMs{1000};
75
    int              m_timeoutMs{1000};
76
    int              m_killTimeoutMs{2000};
77
    int              m_rlimit_as_mbytes{0};
77
    int              m_rlimit_as_mbytes{0};
78
    string           m_stderrFile;
78
    string           m_stderrFile;
79
    // Pipe for data going to the command
79
    // Pipe for data going to the command
80
    int              m_pipein[2]{-1,-1};
80
    int              m_pipein[2]{-1,-1};
81
    std::shared_ptr<NetconCli> m_tocmd;
81
    std::shared_ptr<NetconCli> m_tocmd;
...
...
120
{
120
{
121
    if (mS > 30) {
121
    if (mS > 30) {
122
        m->m_timeoutMs = mS;
122
        m->m_timeoutMs = mS;
123
    }
123
    }
124
}
124
}
125
void ExecCmd::setKillTimeout(int mS)
126
{
127
    m->m_killTimeoutMs = mS;
128
}
125
void ExecCmd::setStderr(const std::string& stderrFile)
129
void ExecCmd::setStderr(const std::string& stderrFile)
126
{
130
{
127
    m->m_stderrFile = stderrFile;
131
    m->m_stderrFile = stderrFile;
128
}
132
}
129
pid_t ExecCmd::getChildPid()
133
pid_t ExecCmd::getChildPid()
...
...
274
        if (m_parent->m_pid > 0 && (grp = getpgid(m_parent->m_pid)) > 0) {
278
        if (m_parent->m_pid > 0 && (grp = getpgid(m_parent->m_pid)) > 0) {
275
            LOGDEB("ExecCmd: pid " << m_parent->m_pid << " killpg(" << grp <<
279
            LOGDEB("ExecCmd: pid " << m_parent->m_pid << " killpg(" << grp <<
276
                   ", SIGTERM)\n");
280
                   ", SIGTERM)\n");
277
            int ret = killpg(grp, SIGTERM);
281
            int ret = killpg(grp, SIGTERM);
278
            if (ret == 0) {
282
            if (ret == 0) {
283
                int ms_slept{0};
279
                for (int i = 0; i < 3; i++) {
284
                for (int i = 0; ; i++) {
280
                    msleep(i == 0 ? 5 : (i == 1 ? 100 : 2000));
285
                    int tosleep = i == 0 ? 5 : (i == 1 ? 100 : 1000);
286
                    msleep(tosleep);
287
                    ms_slept += tosleep;
281
                    int status;
288
                    int status;
282
                    (void)waitpid(m_parent->m_pid, &status, WNOHANG);
289
                    (void)waitpid(m_parent->m_pid, &status, WNOHANG);
283
                    if (kill(m_parent->m_pid, 0) != 0) {
290
                    if (kill(m_parent->m_pid, 0) != 0) {
284
                        break;
291
                        break;
285
                    }
292
                    }
286
                    if (i == 2) {
293
                    // killtimeout == -1 -> never KILL
294
                    if (m_parent->m_killTimeoutMs >= 0 &&
295
                        ms_slept >= m_parent->m_killTimeoutMs) {
287
                        LOGDEB("ExecCmd: killpg(" << (grp) << ", SIGKILL)\n");
296
                        LOGDEB("ExecCmd: killpg(" << grp << ", SIGKILL)\n");
288
                        killpg(grp, SIGKILL);
297
                        killpg(grp, SIGKILL);
289
                        (void)waitpid(m_parent->m_pid, &status, WNOHANG);
298
                        (void)waitpid(m_parent->m_pid, &status, WNOHANG);
299
                        break;
290
                    }
300
                    }
291
                }
301
                }
292
            } else {
302
            } else {
293
                LOGERR("ExecCmd: error killing process group " << (grp) <<
303
                LOGERR("ExecCmd: error killing process group " << (grp) <<
294
                       ": " << errno << "\n");
304
                       ": " << errno << "\n");
...
...
1142
        argv[i++] = it->c_str();
1152
        argv[i++] = it->c_str();
1143
    }
1153
    }
1144
    argv[i] = 0;
1154
    argv[i] = 0;
1145
    execvp(m_argv[0].c_str(), (char *const*)argv);
1155
    execvp(m_argv[0].c_str(), (char *const*)argv);
1146
}
1156
}
1147
1148
1149
////////////////////////////////////////////////////////////////////
1150
#else // TEST
1151
1152
#include <stdio.h>
1153
#include <stdlib.h>
1154
#include <unistd.h>
1155
#include <string.h>
1156
#include <signal.h>
1157
1158
#include <string>
1159
#include <iostream>
1160
#include <sstream>
1161
#include <vector>
1162
1163
#include "log.h"
1164
1165
#include "execmd.h"
1166
#ifdef BUILDING_RECOLL
1167
#include "smallut.h"
1168
#include "cancelcheck.h"
1169
#endif
1170
1171
using namespace std;
1172
1173
#ifdef BUILDING_RECOLL
1174
// Testing the rclexecm protocol outside of recoll. Here we use the
1175
// rcldoc.py filter, you can try with rclaudio too, adjust the file arg
1176
// accordingly
1177
bool exercise_mhexecm(const string& cmdstr, const string& mimetype,
1178
                      vector<string>& files)
1179
{
1180
    ExecCmd cmd;
1181
1182
    vector<string> myparams;
1183
1184
    if (cmd.startExec(cmdstr, myparams, 1, 1) < 0) {
1185
        cerr << "startExec " << cmdstr << " failed. Missing command?\n";
1186
        return false;
1187
    }
1188
1189
    for (vector<string>::const_iterator it = files.begin();
1190
            it != files.end(); it++) {
1191
        // Build request message
1192
        ostringstream obuf;
1193
        obuf << "Filename: " << (*it).length() << "\n" << (*it);
1194
        obuf << "Mimetype: " << mimetype.length() << "\n" << mimetype;
1195
        // Bogus parameter should be skipped by filter
1196
        obuf << "BogusParam: " << string("bogus").length() << "\n" << "bogus";
1197
        obuf << "\n";
1198
        cerr << "SENDING: [" << obuf.str() << "]\n";
1199
        // Send it
1200
        if (cmd.send(obuf.str()) < 0) {
1201
            // The real code calls zapchild here, but we don't need it as
1202
            // this will be handled by ~ExecCmd
1203
            //cmd.zapChild();
1204
            cerr << "send error\n";
1205
            return false;
1206
        }
1207
1208
        // Read answer
1209
        for (int loop = 0;; loop++) {
1210
            string name, data;
1211
1212
            // Code from mh_execm.cpp: readDataElement
1213
            string ibuf;
1214
            // Read name and length
1215
            if (cmd.getline(ibuf) <= 0) {
1216
                cerr << "getline error\n";
1217
                return false;
1218
            }
1219
            // Empty line (end of message)
1220
            if (!ibuf.compare("\n")) {
1221
                cerr << "Got empty line\n";
1222
                name.clear();
1223
                break;
1224
            }
1225
1226
            // Filters will sometimes abort before entering the real
1227
            // protocol, ie if a module can't be loaded. Check the
1228
            // special filter error first word:
1229
            if (ibuf.find("RECFILTERROR ") == 0) {
1230
                cerr << "Got RECFILTERROR\n";
1231
                return false;
1232
            }
1233
1234
            // We're expecting something like Name: len\n
1235
            vector<string> tokens;
1236
            stringToTokens(ibuf, tokens);
1237
            if (tokens.size() != 2) {
1238
                cerr << "bad line in filter output: [" << ibuf << "]\n";
1239
                return false;
1240
            }
1241
            vector<string>::iterator it = tokens.begin();
1242
            name = *it++;
1243
            string& slen = *it;
1244
            int len;
1245
            if (sscanf(slen.c_str(), "%d", &len) != 1) {
1246
                cerr << "bad line in filter output (no len): [" <<
1247
                     ibuf << "]\n";
1248
                return false;
1249
            }
1250
            // Read element data
1251
            data.erase();
1252
            if (len > 0 && cmd.receive(data, len) != len) {
1253
                cerr << "MHExecMultiple: expected " << len <<
1254
                     " bytes of data, got " << data.length() << endl;
1255
                return false;
1256
            }
1257
1258
            // Empty element: end of message
1259
            if (name.empty()) {
1260
                break;
1261
            }
1262
            cerr << "Got name: [" << name << "] data [" << data << "]\n";
1263
        }
1264
    }
1265
    return true;
1266
}
1267
#endif
1268
1269
static char *thisprog;
1270
static char usage [] =
1271
    "trexecmd [-c -r -i -o] cmd [arg1 arg2 ...]\n"
1272
    "   -c : test cancellation (ie: trexecmd -c sleep 1000)\n"
1273
    "   -r : run reexec. Must be separate option.\n"
1274
    "   -i : command takes input\n"
1275
    "   -o : command produces output\n"
1276
    "    If -i is set, we send /etc/group contents to whatever command is run\n"
1277
    "    If -o is set, we print whatever comes out\n"
1278
    "trexecmd -m <filter> <mimetype> <file> [file ...]: test execm:\n"
1279
    "     <filter> should be the path to an execm filter\n"
1280
    "     <mimetype> the type of the file parameters\n"
1281
    "trexecmd -w cmd : do the 'which' thing\n"
1282
    "trexecmd -l cmd test getline\n"
1283
    ;
1284
1285
static void Usage(void)
1286
{
1287
    fprintf(stderr, "%s: usage:\n%s", thisprog, usage);
1288
    exit(1);
1289
}
1290
1291
static int     op_flags;
1292
#define OPT_MOINS 0x1
1293
#define OPT_i     0x4
1294
#define OPT_w     0x8
1295
#define OPT_c     0x10
1296
#define OPT_r     0x20
1297
#define OPT_m     0x40
1298
#define OPT_o     0x80
1299
#define OPT_l     0x100
1300
1301
// Data sink for data coming out of the command. We also use it to set
1302
// a cancellation after a moment.
1303
class MEAdv : public ExecCmdAdvise {
1304
public:
1305
    void newData(int cnt) {
1306
        if (op_flags & OPT_c) {
1307
#ifdef BUILDING_RECOLL
1308
            static int  callcnt;
1309
            if (callcnt++ == 10) {
1310
                // Just sets the cancellation flag
1311
                CancelCheck::instance().setCancel();
1312
                // Would be called from somewhere else and throws an
1313
                // exception. We call it here for simplicity
1314
                CancelCheck::instance().checkCancel();
1315
            }
1316
#endif
1317
        }
1318
        cerr << "newData(" << cnt << ")" << endl;
1319
    }
1320
};
1321
1322
// Data provider, used if the -i flag is set
1323
class MEPv : public ExecCmdProvide {
1324
public:
1325
    FILE *m_fp;
1326
    string *m_input;
1327
    MEPv(string *i)
1328
        : m_input(i) {
1329
        m_fp = fopen("/etc/group", "r");
1330
    }
1331
    ~MEPv() {
1332
        if (m_fp) {
1333
            fclose(m_fp);
1334
        }
1335
    }
1336
    void newData() {
1337
        char line[1024];
1338
        if (m_fp && fgets(line, 1024, m_fp)) {
1339
            m_input->assign((const char *)line);
1340
        } else {
1341
            m_input->erase();
1342
        }
1343
    }
1344
};
1345
1346
1347
1348
ReExec reexec;
1349
int main(int argc, char *argv[])
1350
{
1351
    reexec.init(argc, argv);
1352
1353
    if (0) {
1354
        // Disabled: For testing reexec arg handling
1355
        vector<string> newargs;
1356
        newargs.push_back("newarg");
1357
        newargs.push_back("newarg1");
1358
        newargs.push_back("newarg2");
1359
        newargs.push_back("newarg3");
1360
        newargs.push_back("newarg4");
1361
        reexec.insertArgs(newargs, 2);
1362
    }
1363
1364
    thisprog = argv[0];
1365
    argc--;
1366
    argv++;
1367
1368
    while (argc > 0 && **argv == '-') {
1369
        (*argv)++;
1370
        if (!(**argv))
1371
            /* Cas du "adb - core" */
1372
        {
1373
            Usage();
1374
        }
1375
        while (**argv)
1376
            switch (*(*argv)++) {
1377
            case 'c':
1378
                op_flags |= OPT_c;
1379
                break;
1380
            case 'r':
1381
                op_flags |= OPT_r;
1382
                break;
1383
            case 'w':
1384
                op_flags |= OPT_w;
1385
                break;
1386
#ifdef BUILDING_RECOLL
1387
            case 'm':
1388
                op_flags |= OPT_m;
1389
                break;
1390
#endif
1391
            case 'i':
1392
                op_flags |= OPT_i;
1393
                break;
1394
            case 'l':
1395
                op_flags |= OPT_l;
1396
                break;
1397
            case 'o':
1398
                op_flags |= OPT_o;
1399
                break;
1400
            default:
1401
                Usage();
1402
                break;
1403
            }
1404
        argc--;
1405
        argv++;
1406
    }
1407
1408
    if (argc < 1) {
1409
        Usage();
1410
    }
1411
1412
    string arg1 = *argv++;
1413
    argc--;
1414
    vector<string> l;
1415
    while (argc > 0) {
1416
        l.push_back(*argv++);
1417
        argc--;
1418
    }
1419
1420
#ifdef BUILDING_RECOLL
1421
    DebugLog::getdbl()->setloglevel(DEBDEB1);
1422
    DebugLog::setfilename("stderr");
1423
#endif
1424
    signal(SIGPIPE, SIG_IGN);
1425
1426
    if (op_flags & OPT_r) {
1427
        // Test reexec. Normally only once, next time we fall through
1428
        // because we remove the -r option (only works if it was isolated, not like -rc
1429
        chdir("/");
1430
        argv[0] = strdup("");
1431
        sleep(1);
1432
        cerr << "Calling reexec\n";
1433
        // We remove the -r arg from list, otherwise we are going to
1434
        // loop (which you can try by commenting out the following
1435
        // line)
1436
        reexec.removeArg("-r");
1437
        reexec.reexec();
1438
    }
1439
1440
    if (op_flags & OPT_w) {
1441
        // Test "which" method
1442
        string path;
1443
        if (ExecCmd::which(arg1, path)) {
1444
            cout << path << endl;
1445
            return 0;
1446
        }
1447
        return 1;
1448
#ifdef BUILDING_RECOLL
1449
    } else if (op_flags & OPT_m) {
1450
        if (l.size() < 2) {
1451
            Usage();
1452
        }
1453
        string mimetype = l[0];
1454
        l.erase(l.begin());
1455
        return exercise_mhexecm(arg1, mimetype, l) ? 0 : 1;
1456
#endif
1457
    } else if (op_flags & OPT_l) {
1458
        ExecCmd mexec;
1459
1460
        if (mexec.startExec(arg1, l, false, true) < 0) {
1461
            cerr << "Startexec failed\n";
1462
            exit(1);
1463
        }
1464
        string output;
1465
        int ret = mexec.getline(output, 2);
1466
        cerr << "Got ret " << ret << " output " << output << endl;
1467
        cerr << "Waiting\n";
1468
        int status = mexec.wait();
1469
        cerr << "Got status " << status << endl;
1470
        exit(status);
1471
    } else {
1472
        // Default: execute command line arguments
1473
        ExecCmd mexec;
1474
1475
        // Set callback to be called whenever there is new data
1476
        // available and at a periodic interval, to check for
1477
        // cancellation
1478
        MEAdv adv;
1479
        mexec.setAdvise(&adv);
1480
        mexec.setTimeout(5);
1481
1482
        // Stderr output goes there
1483
        mexec.setStderr("/tmp/trexecStderr");
1484
1485
        // A few environment variables. Check with trexecmd env
1486
        mexec.putenv("TESTVARIABLE1=TESTVALUE1");
1487
        mexec.putenv("TESTVARIABLE2=TESTVALUE2");
1488
        mexec.putenv("TESTVARIABLE3=TESTVALUE3");
1489
1490
        string input, output;
1491
        MEPv  pv(&input);
1492
1493
        string *ip = 0;
1494
        if (op_flags  & OPT_i) {
1495
            ip = &input;
1496
            mexec.setProvide(&pv);
1497
        }
1498
        string *op = 0;
1499
        if (op_flags & OPT_o) {
1500
            op = &output;
1501
        }
1502
1503
        int status = -1;
1504
        try {
1505
            status = mexec.doexec(arg1, l, ip, op);
1506
        } catch (...) {
1507
            cerr << "CANCELLED" << endl;
1508
        }
1509
1510
        fprintf(stderr, "Status: 0x%x\n", status);
1511
        if (op_flags & OPT_o) {
1512
            cout << output;
1513
        }
1514
        exit(status >> 8);
1515
    }
1516
}
1517
#endif // TEST
1518