diff options
Diffstat (limited to 'svdrpclient.c')
-rw-r--r-- | svdrpclient.c | 685 |
1 files changed, 685 insertions, 0 deletions
diff --git a/svdrpclient.c b/svdrpclient.c new file mode 100644 index 0000000..737330e --- /dev/null +++ b/svdrpclient.c @@ -0,0 +1,685 @@ +/* + * svdrpclient.c + * + * See the README file for copyright information + * + */ + + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/select.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <sys/time.h> + +#include <unistd.h> +#include <netdb.h> +#include <fcntl.h> +#include <string.h> +#include <stdlib.h> +#include <ctype.h> +#include <errno.h> + +#include "lib/common.h" + +#include "svdrpclient.h" + +#ifndef VDR_PLUGIN + +ssize_t safe_read(int filedes, void *buffer, size_t size) +{ + for (;;) + { + ssize_t p = read(filedes, buffer, size); + + if (p < 0 && errno == EINTR) + { + tell(0, "EINTR while reading from file handle %d - retrying", filedes); + continue; + } + return p; + } +} + +ssize_t safe_write(int filedes, const void *buffer, size_t size) +{ + ssize_t p = 0; + ssize_t written = size; + const unsigned char *ptr = (const unsigned char *)buffer; + + while (size > 0) + { + p = write(filedes, ptr, size); + + if (p < 0) + { + if (errno == EINTR) + { + tell(0, "EINTR while writing to file handle %d - retrying", filedes); + continue; + } + + break; + } + ptr += p; + size -= p; + } + + return p < 0 ? p : written; +} + +// --- cListObject ----------------------------------------------------------- + +cListObject::cListObject(void) +{ + prev = next = NULL; +} + +cListObject::~cListObject() +{ +} + +void cListObject::Append(cListObject *Object) +{ + next = Object; + Object->prev = this; +} + +void cListObject::Insert(cListObject *Object) +{ + prev = Object; + Object->next = this; +} + +void cListObject::Unlink(void) +{ + if (next) + next->prev = prev; + if (prev) + prev->next = next; + next = prev = NULL; +} + +int cListObject::Index(void) const +{ + cListObject *p = prev; + int i = 0; + + while (p) { + i++; + p = p->prev; + } + return i; +} + +// --- cListBase ------------------------------------------------------------- + +cListBase::cListBase(void) +{ + objects = lastObject = NULL; + count = 0; +} + +cListBase::~cListBase() +{ + Clear(); +} + +void cListBase::Add(cListObject *Object, cListObject *After) +{ + if (After && After != lastObject) { + After->Next()->Insert(Object); + After->Append(Object); + } + else { + if (lastObject) + lastObject->Append(Object); + else + objects = Object; + lastObject = Object; + } + count++; +} + +void cListBase::Ins(cListObject *Object, cListObject *Before) +{ + if (Before && Before != objects) { + Before->Prev()->Append(Object); + Before->Insert(Object); + } + else { + if (objects) + objects->Insert(Object); + else + lastObject = Object; + objects = Object; + } + count++; +} + +void cListBase::Del(cListObject *Object, bool DeleteObject) +{ + if (Object == objects) + objects = Object->Next(); + if (Object == lastObject) + lastObject = Object->Prev(); + Object->Unlink(); + if (DeleteObject) + delete Object; + count--; +} + +void cListBase::Move(int From, int To) +{ + Move(Get(From), Get(To)); +} + +void cListBase::Move(cListObject *From, cListObject *To) +{ + if (From && To && From != To) { + if (From->Index() < To->Index()) + To = To->Next(); + if (From == objects) + objects = From->Next(); + if (From == lastObject) + lastObject = From->Prev(); + From->Unlink(); + if (To) { + if (To->Prev()) + To->Prev()->Append(From); + From->Append(To); + } + else { + lastObject->Append(From); + lastObject = From; + } + if (!From->Prev()) + objects = From; + } +} + +void cListBase::Clear(void) +{ + while (objects) { + cListObject *object = objects->Next(); + delete objects; + objects = object; + } + objects = lastObject = NULL; + count = 0; +} + +cListObject *cListBase::Get(int Index) const +{ + if (Index < 0) + return NULL; + cListObject *object = objects; + while (object && Index-- > 0) + object = object->Next(); + return object; +} + +static int CompareListObjects(const void *a, const void *b) +{ + const cListObject *la = *(const cListObject **)a; + const cListObject *lb = *(const cListObject **)b; + return la->Compare(*lb); +} + +void cListBase::Sort(void) +{ + int n = Count(); + cListObject** a = 0; + cListObject* object = objects; + int i = 0; + + a = (cListObject**)malloc(n * sizeof(cListObject*)); + + while (object && i < n) + { + a[0] = 0; + a[i++] = object; + object = object->Next(); + } + + qsort(a, n, sizeof(cListObject*), CompareListObjects); + objects = lastObject = NULL; + + for (i = 0; i < n; i++) + { + a[i]->Unlink(); + count--; + Add(a[i]); + } + + free(a); +} + +// --- cFile ----------------------------------------------------------------- + +bool cFile::files[FD_SETSIZE] = { false }; +int cFile::maxFiles = 0; + +cFile::cFile(void) +{ + f = -1; +} + +cFile::~cFile() +{ + Close(); +} + +bool cFile::Open(const char *FileName, int Flags, mode_t Mode) +{ + if (!IsOpen()) + return Open(open(FileName, Flags, Mode)); + tell(0, "Error: attempt to re-open %s", FileName); + return false; +} + +bool cFile::Open(int FileDes) +{ + if (FileDes >= 0) { + if (!IsOpen()) { + f = FileDes; + if (f >= 0) { + if (f < FD_SETSIZE) { + if (f >= maxFiles) + maxFiles = f + 1; + if (!files[f]) + files[f] = true; + else + tell(0, "Error: file descriptor %d already in files[]", f); + return true; + } + else + tell(0, "Error: file descriptor %d is larger than FD_SETSIZE (%d)", f, FD_SETSIZE); + } + } + else + tell(0, "Error: attempt to re-open file descriptor %d", FileDes); + } + return false; +} + +void cFile::Close(void) +{ + if (f >= 0) { + close(f); + files[f] = false; + f = -1; + } +} + +bool cFile::Ready(bool Wait) +{ + return f >= 0 && AnyFileReady(f, Wait ? 1000 : 0); +} + +bool cFile::AnyFileReady(int FileDes, int TimeoutMs) +{ + fd_set set; + FD_ZERO(&set); + for (int i = 0; i < maxFiles; i++) { + if (files[i]) + FD_SET(i, &set); + } + if (0 <= FileDes && FileDes < FD_SETSIZE && !files[FileDes]) + FD_SET(FileDes, &set); // in case we come in with an arbitrary descriptor + if (TimeoutMs == 0) + TimeoutMs = 10; // load gets too heavy with 0 + struct timeval timeout; + timeout.tv_sec = TimeoutMs / 1000; + timeout.tv_usec = (TimeoutMs % 1000) * 1000; + return select(FD_SETSIZE, &set, NULL, NULL, &timeout) > 0 && (FileDes < 0 || FD_ISSET(FileDes, &set)); +} + +bool cFile::FileReady(int FileDes, int TimeoutMs) +{ + fd_set set; + struct timeval timeout; + FD_ZERO(&set); + FD_SET(FileDes, &set); + if (TimeoutMs >= 0) { + if (TimeoutMs < 100) + TimeoutMs = 100; + timeout.tv_sec = TimeoutMs / 1000; + timeout.tv_usec = (TimeoutMs % 1000) * 1000; + } + return select(FD_SETSIZE, &set, NULL, NULL, (TimeoutMs >= 0) ? &timeout : NULL) > 0 && FD_ISSET(FileDes, &set); +} + +bool cFile::FileReadyForWriting(int FileDes, int TimeoutMs) +{ + fd_set set; + struct timeval timeout; + FD_ZERO(&set); + FD_SET(FileDes, &set); + if (TimeoutMs < 100) + TimeoutMs = 100; + timeout.tv_sec = 0; + timeout.tv_usec = TimeoutMs * 1000; + return select(FD_SETSIZE, NULL, &set, NULL, &timeout) > 0 && FD_ISSET(FileDes, &set); +} + +#endif // VDR_PLUGIN + +//*************************************************************************** +// SVDRP Client +//*************************************************************************** + +const char* eoc = "\n"; // End Of Command + +cSvdrpClient::cSvdrpClient(const char* aIp, int aPort) +{ + port = aPort; + ip = aIp ? ::strdup(aIp) : 0; + bufSize = BUFSIZ; + buffer = (char*)malloc(bufSize); +} + +cSvdrpClient::~cSvdrpClient(void) +{ + close(); + + free(ip); + free(buffer); +} + +//*************************************************************************** +// Connect +//*************************************************************************** + +int cSvdrpClient::connect() +{ + if (!ip) + { + tell(0, "Error: SVDRPCL: No server IP specified"); + return fail; + } + + struct hostent* hostInfo; + struct sockaddr_in server_addr; + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(port); + in_addr_t remoteAddr; + + if (!isdigit(ip[1])) + { + // map hostname to ip + + if ((hostInfo = ::gethostbyname(ip))) + memcpy((char*)&remoteAddr, hostInfo->h_addr, hostInfo->h_length); + + else if ((remoteAddr = inet_addr(ip)) == INADDR_NONE) + { + tell(0, "Error: SVDRPCL: Can't map hostname '%s' to ip", ip); + return fail; + } + + memcpy(&server_addr.sin_addr, &remoteAddr, sizeof(struct in_addr)); + } + else if (!::inet_aton(ip, &server_addr.sin_addr)) + { + tell(0, "Error: SVDRPCL: Invalid server IP '%s'", ip); + return fail; + } + + int sock = ::socket(PF_INET, SOCK_STREAM, 0); + + if (sock < 0) + { + tell(0, "Error: SVDRPCL: Creating socket for connection to %s: %s failed", ip, strerror(errno)); + return fail; + } + + // set nonblocking + + int flags = ::fcntl(sock, F_GETFL, 0); + + if (flags < 0 || ::fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) + { + tell(0, "Error: SVDRPCL: Unable to use nonblocking I/O for %s: %s", ip, strerror(errno)); + return fail; + } + + if (::connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) + { + if (errno != EINPROGRESS) + { + tell(0, "Error: SVDRPCL: connect to %s:%hu failed: %s", ip, port, strerror(errno)); + return fail; + } + + int result; + fd_set fds; + struct timeval tv; + cMyTimeMs starttime; + int timeout = 3 * 1000; + + do { + FD_ZERO(&fds); + FD_SET(sock, &fds); + tv.tv_usec = (timeout % 1000) * 1000; + tv.tv_sec = timeout / 1000; + result = ::select(sock + 1, 0, &fds, 0, &tv); + + } while (result == -1 && errno == EINTR + && (timeout = 10 * 1000 - starttime.Elapsed()) > 100); + + if (!result) // timeout + { + result = fail; + errno = ETIMEDOUT; + } + else if (result == 1) // check socket for errors + { + int error; + socklen_t size = sizeof(error); + + result = ::getsockopt(sock, SOL_SOCKET, SO_ERROR, &error, &size); + + if (result == 0 && error != 0) + { + result = fail; + errno = error; + } + } + + if (result != 0) + { + tell(0, "Error: SVDRPCL: Cconnecting to '%s:%hu' %s failed", ip, port, strerror(errno)); + ::close(sock); + + return fail; + } + } + + return sock; +} + +int cSvdrpClient::open() +{ + if (file.IsOpen()) + return 0; + + int fd = connect(); + + if (fd < 0) + return fail; + + if (!file.Open(fd)) + { + ::close(fd); + return fail; + } + + // check for greeting + + cList<cLine> greeting; + + if (receive(&greeting) != 220) + { + tell(0, "Error: SVDRPCL: Did not receive greeting from %s. Closing...", ip); + abort(); + + return fail; + } + + const char* msg = 0; + + if (greeting.First() && greeting.First()->Text()) + msg = greeting.First()->Text(); + + tell(2, "SVDRPCL: connected to %s:%hu '%s'", ip, port, msg); + + return success; +} + +void cSvdrpClient::close(int sendQuit) +{ + if (!file.IsOpen()) + return; + + if (sendQuit) + { + if (send("QUIT", false)) + receive(); + } + + file.Close(); +} + +void cSvdrpClient::abort(void) +{ + file.Close(); +} + +int cSvdrpClient::send(const char* cmd, int reconnect) +{ + if (!cmd) + return false; + + if (reconnect && !file.IsOpen()) + open(); + + if (!file.IsOpen()) + { + tell(0, "Error: SVDRPCL: unable to send command to %s. Socket is closed", ip); + return false; + } + + int len = ::strlen(cmd); + + if (safe_write(file, cmd, len) < 0 || safe_write(file, eoc, strlen(eoc)) < 0) + { + tell(0, "Error: SVDRPCL: Writing to %s: %s failed", ip, strerror(errno)); + abort(); + + return false; + } + + return true; +} + +int cSvdrpClient::receive(cList<cLine>* list, int timeoutMs) +{ + // #TODO iconv ?! + + while (readLine(timeoutMs)) + { + char* tail; + long int code = ::strtol(buffer, &tail, 10); + + if (tail - buffer == 3 + && code >= 100 + && code <= 999 + && (*tail == ' ' || *tail == '-')) + { + const char* s = buffer + 4; + + if (code >= 451 && code <= 699) + tell(0, "Error: Got (%ld) '%s'", code, s); + + if (list) + list->Add(new cLine(s)); + + if (*tail == ' ') + return code; + } + else + { + tell(0, "Error: SVDRPCL: Unexpected reply from %s '%s'", ip, buffer); + close(); + break; + } + } + + if (list) + list->Clear(); + + return 0; +} + +int cSvdrpClient::readLine(int timeoutMs) +{ + if (!file.IsOpen()) + return false; + + int tail = 0; + + while (cFile::FileReady(file, timeoutMs)) + { + unsigned char c; + int r = safe_read(file, &c, 1); + + if (r > 0) + { + if (c == *eoc || c == 0x00) + { + // line complete, make sure the string is terminated + + buffer[tail] = 0; + return true; + } + else if ((c <= 0x1F || c == 0x7F) && c != 0x09) + { + // ignore control characters + } + else + { + if (tail >= bufSize - 1) + { + bufSize += BUFSIZ; + buffer = srealloc(buffer, bufSize); + + if (!buffer) + { + tell(0, "Error: SVDRPCL: Unable to increase buffer size to %d byte", bufSize); + close(); + + return false; + } + } + + buffer[tail++] = c; + } + } + else + { + tell(0, "Error: SVDRPCL: Lost connection '%s'", ip); + buffer[0] = 0; + abort(); + + return false; + } + } + + tell(0, "Error: SVDRPCL: Timeout waiting server reply '%s'", ip); + buffer[0] = 0; + abort(); + + return false; +} |