diff options
author | jcid <devnull@localhost> | 2007-10-07 00:36:34 +0200 |
---|---|---|
committer | jcid <devnull@localhost> | 2007-10-07 00:36:34 +0200 |
commit | 93715c46a99c96d6c866968312691ec9ab0f6a03 (patch) | |
tree | 573f19ec6aa740844f53a7c0eb7114f04096bf64 /src/IO/dpi.c |
Initial revision
Diffstat (limited to 'src/IO/dpi.c')
-rw-r--r-- | src/IO/dpi.c | 779 |
1 files changed, 779 insertions, 0 deletions
diff --git a/src/IO/dpi.c b/src/IO/dpi.c new file mode 100644 index 00000000..13cd1f74 --- /dev/null +++ b/src/IO/dpi.c @@ -0,0 +1,779 @@ +/* + * File: dpi.c + * + * Copyright (C) 2002-2006 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +/* + * Dillo plugins (small programs that interact with dillo) + * + * Dillo plugins are designed to handle: + * bookmarks, cookies, FTP, downloads, files, preferences, https, + * datauri and a lot of any-to-html filters. + */ + + +#include <unistd.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> /* for errno */ + +#include <stdio.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> + +#include "../msg.h" +#include "../klist.h" +#include "IO.h" +#include "Url.h" +#include "../misc.h" +#include "../../dpip/dpip.h" + +/* #define DEBUG_LEVEL 2 */ +#define DEBUG_LEVEL 4 +#include "../debug.h" + +/* This one is tricky, some sources state it should include the byte + * for the terminating NULL, and others say it shouldn't. */ +# define D_SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) \ + + strlen ((ptr)->sun_path)) + +/* Solaris may not have this one... */ +#ifndef AF_LOCAL +#define AF_LOCAL AF_UNIX +#endif + + +typedef struct { + int InTag; + int Send2EOF; + + int DataTotalSize; + int DataRecvSize; + + Dstr *Buf; + + int BufIdx; + int TokIdx; + int TokSize; + int TokIsTag; + + ChainLink *InfoRecv; + int Key; +} dpi_conn_t; + + +/* + * Local data + */ +static Klist_t *ValidConns = NULL; /* Active connections list. It holds + * pointers to dpi_conn_t structures. */ + + +/* + * Initialize local data + */ +void a_Dpi_init(void) +{ + /* empty */ +} + +/* + * Close a FD handling EINTR + */ +static void Dpi_close_fd(int fd) +{ + int st; + + do + st = close(fd); + while (st < 0 && errno == EINTR); +} + +/* + * Create a new connection data structure + */ +static dpi_conn_t *Dpi_conn_new(ChainLink *Info) +{ + dpi_conn_t *conn = dNew0(dpi_conn_t, 1); + + conn->Buf = dStr_sized_new(8*1024); + conn->InfoRecv = Info; + conn->Key = a_Klist_insert(&ValidConns, conn); + + return conn; +} + +/* + * Free a connection data structure + */ +static void Dpi_conn_free(dpi_conn_t *conn) +{ + a_Klist_remove(ValidConns, conn->Key); + dStr_free(conn->Buf, 1); + dFree(conn); +} + +/* + * Check whether a conn is still valid. + * Return: 1 if found, 0 otherwise + */ +int Dpi_conn_valid(int key) +{ + return (a_Klist_get_data(ValidConns, key)) ? 1 : 0; +} + +/* + * Append the new buffer in 'dbuf' to Buf in 'conn' + */ +static void Dpi_append_dbuf(dpi_conn_t *conn, DataBuf *dbuf) +{ + if (dbuf->Code == 0 && dbuf->Size > 0) { + dStr_append_l(conn->Buf, dbuf->Buf, dbuf->Size); + } +} + +/* + * Split the data stream into tokens. + * Here, a token is either: + * a) a dpi tag + * b) a raw data chunk + * + * Return Value: 0 upon a new token, -1 on not enough data. + * + * TODO: define an API and move this function into libDpip.a. +*/ +static int Dpi_get_token(dpi_conn_t *conn) +{ + int i, resp = -1; + char *buf = conn->Buf->str; + + if (conn->BufIdx == conn->Buf->len) { + dStr_truncate(conn->Buf, 0); + conn->BufIdx = 0; + return resp; + } + + if (conn->Send2EOF) { + conn->TokIdx = conn->BufIdx; + conn->TokSize = conn->Buf->len - conn->BufIdx; + conn->BufIdx = conn->Buf->len; + return 0; + } + + _MSG("conn->BufIdx = %d; conn->Buf->len = %d\nbuf: [%s]\n", + conn->BufIdx,conn->Buf->len, conn->Buf->str + conn->BufIdx); + + if (!conn->InTag) { + /* search for start of tag */ + while (conn->BufIdx < conn->Buf->len && buf[conn->BufIdx] != '<') + ++conn->BufIdx; + if (conn->BufIdx < conn->Buf->len) { + /* found */ + conn->InTag = 1; + conn->TokIdx = conn->BufIdx; + } else { + MSG_ERR("[Dpi_get_token] Can't find token start\n"); + } + } + + if (conn->InTag) { + /* search for end of tag (EOT=" '>") */ + for (i = conn->BufIdx; i < conn->Buf->len; ++i) + if (buf[i] == '>' && i >= 2 && buf[i-1] == '\'' && buf[i-2] == ' ') + break; + conn->BufIdx = i; + + if (conn->BufIdx < conn->Buf->len) { + /* found EOT */ + conn->TokIsTag = 1; + conn->TokSize = conn->BufIdx - conn->TokIdx + 1; + ++conn->BufIdx; + conn->InTag = 0; + resp = 0; + } + } + + return resp; +} + +/* + * Parse a dpi tag and take the appropriate actions + */ +static void Dpi_parse_token(dpi_conn_t *conn) +{ + char *tag, *cmd, *msg, *urlstr; + DataBuf *dbuf; + char *Tok = conn->Buf->str + conn->TokIdx; + + if (conn->Send2EOF) { + /* we're receiving data chunks from a HTML page */ + dbuf = a_Chain_dbuf_new(Tok, conn->TokSize, 0); + a_Chain_fcb(OpSend, conn->InfoRecv, dbuf, "send_page_2eof"); + dFree(dbuf); + return; + } + + tag = dStrndup(Tok, (size_t)conn->TokSize); + _MSG("Dpi_parse_token: {%s}\n", tag); + + cmd = a_Dpip_get_attr(Tok, conn->TokSize, "cmd"); + if (strcmp(cmd, "send_status_message") == 0) { + msg = a_Dpip_get_attr(Tok, conn->TokSize, "msg"); + a_Chain_fcb(OpSend, conn->InfoRecv, msg, cmd); + dFree(msg); + + } else if (strcmp(cmd, "chat") == 0) { + msg = a_Dpip_get_attr(Tok, conn->TokSize, "msg"); + a_Chain_fcb(OpSend, conn->InfoRecv, msg, cmd); + dFree(msg); + + } else if (strcmp(cmd, "dialog") == 0) { + /* For now will send the dpip tag... */ + a_Chain_fcb(OpSend, conn->InfoRecv, tag, cmd); + + } else if (strcmp(cmd, "start_send_page") == 0) { + conn->Send2EOF = 1; + urlstr = a_Dpip_get_attr(Tok, conn->TokSize, "url"); + a_Chain_fcb(OpSend, conn->InfoRecv, urlstr, cmd); + dFree(urlstr); + /* todo: a_Dpip_get_attr(Tok, conn->TokSize, "send_mode") */ + + } else if (strcmp(cmd, "reload_request") == 0) { + urlstr = a_Dpip_get_attr(Tok, conn->TokSize, "url"); + a_Chain_fcb(OpSend, conn->InfoRecv, urlstr, cmd); + dFree(urlstr); + } + dFree(cmd); + + dFree(tag); +} + + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* + * Get a new data buffer (within a 'dbuf'), save it into local data, + * split in tokens and parse the contents. + */ +static void Dpi_process_dbuf(int Op, void *Data1, dpi_conn_t *conn) +{ + DataBuf *dbuf = Data1; + int key = conn->Key; + + /* Very useful for debugging: show the data stream as received. */ + /* fwrite(dbuf->Buf, dbuf->Size, 1, stdout); */ + + if (Op == IORead) { + Dpi_append_dbuf(conn, dbuf); + /* 'conn' has to be validated because Dpi_parse_token() MAY call abort */ + while (Dpi_conn_valid(key) && Dpi_get_token(conn) != -1) { + Dpi_parse_token(conn); + } + + } else if (Op == IOClose) { + /* unused */ + } +} + +/* + * Start dpid. + * Return: 0 starting now, 1 Error. + */ +static int Dpi_start_dpid(void) +{ + pid_t pid; + int st_pipe[2], n, ret = 1; + char buf[16]; + + /* create a pipe to track our child's status */ + if (pipe(st_pipe)) + return 1; + + pid = fork(); + if (pid == 0) { + /* This is the child process. Execute the command. */ + char *path1 = dStrconcat(dGethomedir(), "/.dillo/dpid", NULL); + Dpi_close_fd(st_pipe[0]); + if (execl(path1, "dpid", NULL) == -1) { + dFree(path1); + if (execlp("dpid", "dpid", NULL) == -1) { + DEBUG_MSG(4, "Dpi_start_dpid (child): %s\n", dStrerror(errno)); + do + n = write(st_pipe[1], "ERROR", 5); + while (n == -1 && errno == EINTR); + Dpi_close_fd(st_pipe[1]); + _exit (EXIT_FAILURE); + } + } + } else if (pid < 0) { + /* The fork failed. Report failure. */ + DEBUG_MSG(4, "Dpi_start_dpid: %s\n", dStrerror(errno)); + /* close the unused pipe */ + Dpi_close_fd(st_pipe[0]); + Dpi_close_fd(st_pipe[1]); + + } else { + /* This is the parent process, check our child status... */ + Dpi_close_fd(st_pipe[1]); + do + n = read(st_pipe[0], buf, 16); + while (n == -1 && errno == EINTR); + DEBUG_MSG(2, "Dpi_start_dpid: n = %d\n", n); + if (n != 5) { + ret = 0; + } else { + DEBUG_MSG(4, "Dpi_start_dpid: %s\n", dStrerror(errno)); + } + } + + return ret; +} + +/* + * Make a connection test for a UDS. + * Return: 0 OK, 1 Not working. + */ +static int Dpi_check_uds(char *uds_name) +{ + struct sockaddr_un pun; + int SockFD, ret = 1; + + if (access(uds_name, W_OK) == 0) { + /* socket connection test */ + memset(&pun, 0, sizeof(struct sockaddr_un)); + pun.sun_family = AF_LOCAL; + strncpy(pun.sun_path, uds_name, sizeof (pun.sun_path)); + + if ((SockFD = socket(AF_LOCAL, SOCK_STREAM, 0)) == -1 || + connect(SockFD, (void*)&pun, D_SUN_LEN(&pun)) == -1) { + DEBUG_MSG(4, "Dpi_check_uds: %s %s\n", dStrerror(errno), uds_name); + } else { + Dpi_close_fd(SockFD); + ret = 0; + } + } + return ret; +} + +/* + * Return the directory where the UDS are in, + * NULL if it can't be found. + */ +static char *Dpi_get_dpid_uds_dir(void) +{ + FILE *in; + char *saved_name_filename; /* :) */ + char dpid_uds_dir[256], *p = NULL; + + saved_name_filename = + dStrconcat(dGethomedir(), "/.dillo/dpi_socket_dir", NULL); + in = fopen(saved_name_filename, "r"); + dFree(saved_name_filename); + + if (in != NULL) { + fgets(dpid_uds_dir, 256, in); + fclose(in); + if ((p = strchr(dpid_uds_dir, '\n'))) { + *p = 0; + } + if (access(dpid_uds_dir, F_OK) == 0) { + p = dStrdup(dpid_uds_dir); + _MSG("Dpi_get_dpid_uds_dir:: %s\n", p); + } + } + + _MSG("Dpi_get_dpid_uds_dir: %s \n", dStrerror(errno)); + return p; +} + +/* + * Return the dpid's UDS name, NULL on failure. + */ +static char *Dpi_get_dpid_uds_name(void) +{ + char *dpid_uds_dir, *dpid_uds_name = NULL; + + if ((dpid_uds_dir = Dpi_get_dpid_uds_dir()) != NULL) + dpid_uds_name= dStrconcat(dpid_uds_dir, "/", "dpid.srs", NULL); + + dFree(dpid_uds_dir); + return dpid_uds_name; +} + +/* + * Confirm that the dpid is running. If not, start it. + * Return: 0 running OK, 1 starting (EAGAIN), 2 Error. + */ +static int Dpi_check_dpid(int num_tries) +{ + static int starting = 0; + char *dpid_uds_name; + int check_st = 1, ret = 2; + + if ((dpid_uds_name = Dpi_get_dpid_uds_name())) + check_st = Dpi_check_uds(dpid_uds_name); + + _MSG("Dpi_check_dpid: dpid_uds_name=%s, check_st=%d\n", + dpid_uds_name, check_st); + + if (check_st == 0) { + /* connection test with dpi server passed */ + starting = 0; + ret = 0; + } else if (!dpid_uds_name || check_st) { + if (!starting) { + /* start dpid */ + if (Dpi_start_dpid() == 0) { + starting = 1; + ret = 1; + } + } else if (++starting < num_tries) { + ret = 1; + } else { + /* we waited too much, report an error... */ + starting = 0; + } + } + + dFree(dpid_uds_name); + DEBUG_MSG(2, "Dpi_check_dpid:: %s\n", + (ret == 0) ? "OK" : (ret == 1 ? "EAGAIN" : "ERROR")); + return ret; +} + +/* + * Confirm that the dpid is running. If not, start it. + * Return: 0 running OK, 2 Error. + */ +static int Dpi_blocking_start_dpid(void) +{ + int cst, try = 0, + n_tries = 12; /* 3 seconds */ + + /* test the dpid, and wait a bit for it to start if necessary */ + while ((cst = Dpi_check_dpid(n_tries)) == 1) { + MSG("Dpi_blocking_start_dpid: try %d\n", ++try); + usleep(250000); /* 1/4 sec */ + } + return cst; +} + +/* + * Return the UDS name of a dpi server. + * (A query is sent to dpid and then its answer parsed) + * note: as the available servers and/or the dpi socket directory can + * change at any time, we'll ask each time. If someday we find + * that connecting each time significantly degrades performance, + * an optimized approach can be tried. + */ +static char *Dpi_get_server_uds_name(const char *server_name) +{ + char *dpid_uds_dir, *dpid_uds_name = NULL, + *server_uds_name = NULL; + int st; + + dReturn_val_if_fail (server_name != NULL, NULL); + DEBUG_MSG(2, "Dpi_get_server_uds_name:: server_name = [%s]\n", server_name); + + dpid_uds_dir = Dpi_get_dpid_uds_dir(); + if (dpid_uds_dir) { + struct sockaddr_un dpid; + int sock, req_sz, rdlen; + char buf[128], *cmd, *request, *rply; + size_t buflen; + + /* Get the server's uds name from dpid */ + sock = socket(AF_LOCAL, SOCK_STREAM, 0); + dpid.sun_family = AF_LOCAL; + dpid_uds_name = dStrconcat(dpid_uds_dir, "/", "dpid.srs", NULL); + _MSG("dpid_uds_name = [%s]\n", dpid_uds_name); + strncpy(dpid.sun_path, dpid_uds_name, sizeof(dpid.sun_path)); + + if (connect(sock, (struct sockaddr *) &dpid, sizeof(dpid)) == -1) + perror("connect"); + /* ask dpid to check the server plugin and send its UDS name back */ + request = a_Dpip_build_cmd("cmd=%s msg=%s", "check_server", server_name); + DEBUG_MSG(2, "[%s]\n", request); + do + st = write(sock, request, strlen(request)); + while (st < 0 && errno == EINTR); + if (st < 0 && errno != EINTR) + perror("writing request"); + dFree(request); + shutdown(sock, 1); /* signals no more writes to dpid */ + + /* Get the reply */ + rply = NULL; + buf[0] = '\0'; + buflen = sizeof(buf)/sizeof(buf[0]); + for (req_sz = 0; (rdlen = read(sock, buf, buflen)) != 0; + req_sz += rdlen) { + if (rdlen == -1 && errno == EINTR) + continue; + if (rdlen == -1) { + perror(" ** Dpi_get_server_uds_name **"); + break; + } + rply = dRealloc(rply, (uint_t)(req_sz + rdlen + 1)); + if (req_sz == 0) + rply[0] = '\0'; + strncat(rply, buf, (size_t)rdlen); + } + Dpi_close_fd(sock); + DEBUG_MSG(2, "rply = [%s]\n", rply); + + /* Parse reply */ + if (rdlen == 0 && rply) { + cmd = a_Dpip_get_attr(rply, (int)strlen(rply), "cmd"); + if (strcmp(cmd, "send_data") == 0) + server_uds_name = a_Dpip_get_attr(rply, (int)strlen(rply), "msg"); + dFree(cmd); + dFree(rply); + } + } + dFree(dpid_uds_dir); + dFree(dpid_uds_name); + DEBUG_MSG(2, "Dpi_get_server_uds_name:: %s\n", server_uds_name); + return server_uds_name; +} + + +/* + * Connect a socket to a dpi server and return the socket's FD. + * We have to ask 'dpid' (dpi daemon) for the UDS of the target dpi server. + * Once we have it, then the proper file descriptor is returned (-1 on error). + */ +static int Dpi_connect_socket(const char *server_name, int retry) +{ + char *server_uds_name; + struct sockaddr_un pun; + int SockFD, err; + + /* Query dpid for the UDS name for this server */ + server_uds_name = Dpi_get_server_uds_name(server_name); + DEBUG_MSG(2, "server_uds_name = [%s]\n", server_uds_name); + + if (access(server_uds_name, F_OK) != 0) { + MSG("server socket was NOT found\n"); + return -1; + } + + /* connect with this server's socket */ + memset(&pun, 0, sizeof(struct sockaddr_un)); + pun.sun_family = AF_LOCAL; + strncpy(pun.sun_path, server_uds_name, sizeof (pun.sun_path)); + dFree(server_uds_name); + + if ((SockFD = socket(AF_LOCAL, SOCK_STREAM, 0)) == -1) + perror("[dpi::socket]"); + else if (connect(SockFD, (void*)&pun, D_SUN_LEN(&pun)) == -1) { + err = errno; + SockFD = -1; + MSG("[dpi::connect] errno:%d %s\n", errno, dStrerror(errno)); + if (retry) { + switch (err) { + case ECONNREFUSED: case EBADF: case ENOTSOCK: case EADDRNOTAVAIL: + /* the server may crash and its socket name survive */ + unlink(pun.sun_path); + SockFD = Dpi_connect_socket(server_name, FALSE); + break; + } + } + } + + return SockFD; +} + + +/* + * CCC function for the Dpi module + */ +void a_Dpi_ccc(int Op, int Branch, int Dir, ChainLink *Info, + void *Data1, void *Data2) +{ + dpi_conn_t *conn; + int SockFD = -1, st; + + a_Chain_debug_msg("a_Dpi_ccc", Op, Branch, Dir); + + if (Branch == 1) { + if (Dir == BCK) { + /* Send commands to dpi-server */ + switch (Op) { + case OpStart: + if ((st = Dpi_blocking_start_dpid()) == 0) { + SockFD = Dpi_connect_socket(Data1, TRUE); + if (SockFD != -1) { + int *fd = dNew(int, 1); + *fd = SockFD; + Info->LocalKey = fd; + a_Chain_link_new(Info, a_Dpi_ccc, BCK, a_IO_ccc, 1, 1); + a_Chain_bcb(OpStart, Info, Info->LocalKey, NULL); + /* tell the capi to start the receiving branch */ + a_Chain_fcb(OpSend, Info, Info->LocalKey, "SockFD"); + } + } + + if (st == 0 && SockFD != -1) { + a_Chain_fcb(OpSend, Info, NULL, "DpidOK"); + } else { + MSG_ERR("dpi.c: can't start dpi daemon\n"); + a_Dpi_ccc(OpAbort, 1, FWD, Info, NULL, "DpidERROR"); + } + break; + case OpSend: + a_Chain_bcb(OpSend, Info, Data1, NULL); + break; + case OpEnd: + a_Chain_bcb(OpEnd, Info, NULL, NULL); + dFree(Info->LocalKey); + dFree(Info); + break; + default: + MSG_WARN("Unused CCC\n"); + break; + } + } else { /* FWD */ + /* Send commands to dpi-server (status) */ + switch (Op) { + case OpAbort: + a_Chain_fcb(OpAbort, Info, NULL, Data2); + dFree(Info); + break; + default: + MSG_WARN("Unused CCC\n"); + break; + } + } + + } else if (Branch == 2) { + if (Dir == FWD) { + /* Receiving from server */ + switch (Op) { + case OpSend: + /* Data1 = dbuf */ + Dpi_process_dbuf(IORead, Data1, Info->LocalKey); + break; + case OpEnd: + a_Chain_fcb(OpEnd, Info, NULL, NULL); + Dpi_conn_free(Info->LocalKey); + dFree(Info); + break; + default: + MSG_WARN("Unused CCC\n"); + break; + } + } else { /* BCK */ + switch (Op) { + case OpStart: + conn = Dpi_conn_new(Info); + Info->LocalKey = conn; + + /* Hack: for receiving HTTP through the DPI framework */ + if (strcmp(Data2, "http") == 0) { + conn->Send2EOF = 1; + } + + a_Chain_link_new(Info, a_Dpi_ccc, BCK, a_IO_ccc, 2, 2); + a_Chain_bcb(OpStart, Info, NULL, Data1); /* IORead, SockFD */ + break; + case OpAbort: + a_Chain_bcb(OpAbort, Info, NULL, NULL); + Dpi_conn_free(Info->LocalKey); + dFree(Info); + break; + default: + MSG_WARN("Unused CCC\n"); + break; + } + } + } +} + +/*! Send DpiBye to dpid + * Note: currently disabled. Maybe it'd be better to have a + * dpid_idle_timeout variable in the config file. + */ +void a_Dpi_bye_dpid() +{ + char *DpiBye_cmd; + struct sockaddr_un sa; + size_t sun_path_len, addr_len; + char *srs_name; + int new_socket; + + srs_name = Dpi_get_dpid_uds_name(); + sun_path_len = sizeof(sa.sun_path); + addr_len = sizeof(sa); + + sa.sun_family = AF_LOCAL; + + if ((new_socket = socket(AF_LOCAL, SOCK_STREAM, 0)) == -1) { + DEBUG_MSG(4, "a_Dpi_bye_dpid: %s\n", dStrerror(errno)); + } + strncpy(sa.sun_path, srs_name, sizeof (sa.sun_path)); + if (connect(new_socket, (struct sockaddr *) &sa, addr_len) == -1) { + DEBUG_MSG(4, "a_Dpi_bye_dpid: %s\n", dStrerror(errno)); + MSG("%s\n", sa.sun_path); + } + DpiBye_cmd = a_Dpip_build_cmd("cmd=%s", "DpiBye"); + (void) write(new_socket, DpiBye_cmd, strlen(DpiBye_cmd)); + dFree(DpiBye_cmd); + Dpi_close_fd(new_socket); +} + + +/* + * Send a command to a dpi server, and block until the answer is got. + * Return value: the dpip tag answer as an string, NULL on error. + */ +char *a_Dpi_send_blocking_cmd(const char *server_name, const char *cmd) +{ + int cst, SockFD; + ssize_t st; + char buf[16384], *retval = NULL; + + /* test the dpid, and wait a bit for it to start if necessary */ + if ((cst = Dpi_blocking_start_dpid()) != 0) { + return retval; + } + + SockFD = Dpi_connect_socket(server_name, TRUE); + if (SockFD != -1) { + /* todo: handle the case of (st < strlen(cmd)) */ + do + st = write(SockFD, cmd, strlen(cmd)); + while (st == -1 && errno == EINTR); + + /* todo: if the answer is too long... */ + do + st = read(SockFD, buf, 16384); + while (st < 0 && errno == EINTR); + + if (st == -1) + perror("[a_Dpi_send_blocking_cmd]"); + else if (st > 0) + retval = dStrndup(buf, (size_t)st); + + Dpi_close_fd(SockFD); + + } else { + perror("[a_Dpi_send_blocking_cmd]"); + } + + return retval; +} + |