-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathprotocolext.c
184 lines (170 loc) · 5.17 KB
/
protocolext.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
/*
* protocolext.c: IPTV plugin for the Video Disk Recorder
*
* See the README file for copyright information and how to reach the author.
*
*/
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <fcntl.h>
#include <unistd.h>
#include <vdr/device.h>
#include <vdr/plugin.h>
#include "common.h"
#include "config.h"
#include "log.h"
#include "protocolext.h"
#ifndef EXTSHELL
#define EXTSHELL "/bin/bash"
#endif
cIptvProtocolExt::cIptvProtocolExt()
: pidM(-1),
scriptFileM(""),
scriptParameterM(0),
streamPortM(0)
{
debug1("%s", __PRETTY_FUNCTION__);
}
cIptvProtocolExt::~cIptvProtocolExt()
{
debug1("%s", __PRETTY_FUNCTION__);
// Drop the socket connection
cIptvProtocolExt::Close();
}
void cIptvProtocolExt::ExecuteScript(void)
{
debug1("%s", __PRETTY_FUNCTION__);
// Check if already executing
if (isActiveM || isempty(scriptFileM))
return;
if (pidM > 0) {
error("Cannot execute script!");
return;
}
// Let's fork
ERROR_IF_RET((pidM = fork()) == -1, "fork()", return);
// Check if child process
if (pidM == 0) {
// Close all dup'ed filedescriptors
int MaxPossibleFileDescriptors = getdtablesize();
for (int i = STDERR_FILENO + 1; i < MaxPossibleFileDescriptors; i++)
close(i);
// Execute the external script
cString cmd = cString::sprintf("%s %d %d", *scriptFileM, scriptParameterM, streamPortM);
debug1("%s Child %s", __PRETTY_FUNCTION__, *cmd);
// Create a new session for a process group
ERROR_IF_RET(setsid() == -1, "setsid()", _exit(-1));
if (execl(EXTSHELL, "sh", "-c", *cmd, (char *)NULL) == -1) {
error("Script execution failed: %s", *cmd);
_exit(-1);
}
_exit(0);
}
else {
debug1("%s pid=%d", __PRETTY_FUNCTION__, pidM);
}
}
void cIptvProtocolExt::TerminateScript(void)
{
debug1("%s pid=%d", __PRETTY_FUNCTION__, pidM);
if (!isActiveM || isempty(scriptFileM))
return;
if (pidM > 0) {
const unsigned int timeoutms = 100;
unsigned int waitms = 0;
bool waitOver = false;
// Signal and wait for termination
int retval = killpg(pidM, SIGINT);
ERROR_IF_RET(retval < 0, "kill()", waitOver = true);
while (!waitOver) {
retval = 0;
waitms += timeoutms;
if ((waitms % 2000) == 0) {
error("Script '%s' won't terminate - killing it!", *scriptFileM);
killpg(pidM, SIGKILL);
}
// Clear wait status to make sure child exit status is accessible
// and wait for child termination
#ifdef __FreeBSD__
int waitStatus = 0;
retval = waitpid(pidM, &waitStatus, WNOHANG);
#else // __FreeBSD__
siginfo_t waitStatus;
memset(&waitStatus, 0, sizeof(waitStatus));
retval = waitid(P_PID, pidM, &waitStatus, (WNOHANG | WEXITED));
#endif // __FreeBSD__
ERROR_IF_RET(retval < 0, "waitid()", waitOver = true);
// These are the acceptable conditions under which child exit is
// regarded as successful
#ifdef __FreeBSD__
if (retval > 0 && (WIFEXITED(waitStatus) || WIFSIGNALED(waitStatus))) {
#else // __FreeBSD__
if (!retval && waitStatus.si_pid && (waitStatus.si_pid == pidM) &&
((waitStatus.si_code == CLD_EXITED) || (waitStatus.si_code == CLD_KILLED))) {
#endif // __FreeBSD__
debug1("%s Child (%d) exited as expected", __PRETTY_FUNCTION__, pidM);
waitOver = true;
}
// Unsuccessful wait, avoid busy looping
if (!waitOver)
cCondWait::SleepMs(timeoutms);
}
pidM = -1;
}
}
bool cIptvProtocolExt::Open(void)
{
debug1("%s", __PRETTY_FUNCTION__);
// Reject empty script files
if (!strlen(*scriptFileM))
return false;
// Create the listening socket
OpenSocket(streamPortM);
// Execute the external script
ExecuteScript();
isActiveM = true;
return true;
}
bool cIptvProtocolExt::Close(void)
{
debug1("%s", __PRETTY_FUNCTION__);
// Terminate the external script
TerminateScript();
isActiveM = false;
// Close the socket
CloseSocket();
return true;
}
int cIptvProtocolExt::Read(unsigned char* bufferAddrP, unsigned int bufferLenP)
{
return cIptvUdpSocket::Read(bufferAddrP, bufferLenP);
}
bool cIptvProtocolExt::SetSource(const char* locationP, const int parameterP, const int indexP)
{
debug1("%s (%s, %d, %d)", __PRETTY_FUNCTION__, locationP, parameterP, indexP);
if (!isempty(locationP)) {
struct stat stbuf;
// Update script file and parameter
scriptFileM = cString::sprintf("%s/%s", IptvConfig.GetResourceDirectory(), locationP);
if ((stat(*scriptFileM, &stbuf) != 0) || (strstr(*scriptFileM, "..") != 0)) {
error("Non-existent or relative path script '%s'", *scriptFileM);
return false;
}
scriptParameterM = parameterP;
// Update listen port
streamPortM = IptvConfig.GetProtocolBasePort() + indexP * 2;
}
return true;
}
bool cIptvProtocolExt::SetPid(int pidP, int typeP, bool onP)
{
debug16("%s (%d, %d, %d)", __PRETTY_FUNCTION__, pidP, typeP, onP);
return true;
}
cString cIptvProtocolExt::GetInformation(void)
{
debug16("%s", __PRETTY_FUNCTION__);
return cString::sprintf("ext://%s:%d", *scriptFileM, scriptParameterM);
}