This repository has been archived on 2025-02-12. You can view files and clone it, but cannot push or open issues or pull requests.
NeoStats/sqlsrv/api.c

699 lines
20 KiB
C

/***************************************************************
* Run Time Access
* Copyright (C) 2003 Robert W Smith (bsmith@linuxtoys.org)
*
* This program is distributed under the terms of the GNU LGPL.
* See the file COPYING file.
**************************************************************/
/***************************************************************
* api.c -- routines to provide a PostgreSQL DB API to
* embedded systems.
**************************************************************/
#include <stdio.h>
#include <stdlib.h> /* for mkstemp() */
#include <libgen.h> /* for dirname() */
#include <string.h> /* for strlen() */
#include <limits.h> /* for PATH_MAX */
#ifdef SYSLOG
#include <syslog.h>
#endif
#include <time.h>
#include "rta.h" /* for various constants */
#include "do_sql.h" /* for LOC */
/* Tbl and Col contain pointers to table and column
* definitions of all tables and columns in * the system.
* Ntbl and Ncol are the number of tables and columns in each
* list. These are used often enough that they are globals. */
TBLDEF *Tbl[MX_TBL];
int Ntbl;
COLDEF *Col[MX_COL];
int Ncol;
extern struct EpgDbg rtadbg;
EpgConn *getconndata(int id);
EpgConn *
getconndata(int id)
{
lnode_t *lnode;
EpgConn *conn;
lnode = list_first(pgconn);
while (lnode) {
conn = lnode_get(lnode);
if (conn->id == id)
return conn;
lnode = list_next(pgconn, lnode);
}
return NULL;
}
/***************************************************************
* rta_init(): - Initialize all internal system tables.
*
* Input: None.
* Output: None.
**************************************************************/
void
rta_init(logcb logfunc)
{
int i; /* loop index */
extern TBLDEF pg_userTable;
extern TBLDEF rta_tablesTable;
extern TBLDEF rta_columnsTable;
#ifdef SHWDBGTBL
extern TBLDEF rta_dbgTable;
#endif
extern TBLDEF rta_statTable;
extern TBLDEF pg_connTable;
#ifdef SYSLOG
extern void restart_syslog();
#else
RTA_Conf.loggingfunc = logfunc;
#endif
for (i = 0; i < MX_TBL; i++)
{
Tbl[i] = (TBLDEF *) 0;
}
Ntbl = 0;
/* init the pgconn structure */
pgconn = list_create(-1);
pg_connTable.address = pgconn;
/* add system and internal tables here */
(void) rta_add_table(&rta_tablesTable);
(void) rta_add_table(&rta_columnsTable);
(void) rta_add_table(&pg_userTable);
(void) rta_add_table(&pg_connTable);
#ifdef SHWDBGTBL
(void) rta_add_table(&rta_dbgTable);
#endif
(void) rta_add_table(&rta_statTable);
#ifdef SYSLOG
restart_syslog((char *) 0, (char *) 0, (char *) 0, 0);
#endif
}
/***************************************************************
* rta_add_table(): - Add one table to the list of
* tables in the system. If the table has an associated
* "savefile" we try to open the savefile and execute any SQL
* commands found there.
*
* Input: ptbl: pointer to the table to add
* Output: RTA_SUCCESS - Add successful
* RTA_DUP - Table is already in the list. (Note
* that this might not be an error since
* we can allow redefinition of a table)
* RTA_ERROR - The passed table definition has a
* problem which prevents its addition.
* A syslog error message describes the
* problem
**************************************************************/
int
rta_add_table(TBLDEF *ptbl)
{
extern struct EpgStat rtastat;
extern TBLDEF rta_columnsTable;
int i, j; /* a loop index */
/* Error if at Ntbl limit */
if (Ntbl == MX_TBL)
{
rtastat.nrtaerr++;
if (rtadbg.rtaerr)
rtalog(LOC, Er_Max_Tbls);
return (RTA_ERROR);
}
/* verify that table name is unique */
i = 0;
while (i < Ntbl)
{
if (!strncmp(ptbl->name, Tbl[i]->name, MXTBLNAME))
{
rtastat.nrtaerr++;
if (rtadbg.rtaerr)
rtalog(LOC, Er_Tbl_Dup, ptbl->name);
return (RTA_ERROR);
}
i++;
}
/* verify lenght of table name */
if (strlen(ptbl->name) > MXTBLNAME)
{
rtastat.nrtaerr++;
if (rtadbg.rtaerr)
rtalog(LOC, Er_Tname_Big, ptbl->name);
return (RTA_ERROR);
}
/* verify savefile name is a valid pointer */
if (ptbl->savefile == (char *) 0)
{
rtastat.nrtaerr++;
if (rtadbg.rtaerr)
rtalog(LOC, Er_Col_Type, "savefile");
return (RTA_ERROR);
}
/* Check the upper bound on # columns / table */
if (ptbl->ncol > NCMDCOLS)
{
rtastat.nrtaerr++;
if (rtadbg.rtaerr)
rtalog(LOC, Er_Cmd_Cols, ptbl->name);
return (RTA_ERROR);
}
/* verify that column names are unique within table */
for (i = 0; i < ptbl->ncol; i++)
{
for (j = 0; j < i; j++)
{
if (!strncmp(ptbl->cols[i].name, ptbl->cols[j].name, MXCOLNAME))
{
rtastat.nrtaerr++;
if (rtadbg.rtaerr)
rtalog(LOC, Er_Col_Dup, ptbl->cols[i].name);
return (RTA_ERROR);
}
}
}
/* verify column name length, help length, data type, and flag
contents */
for (i = 0; i < ptbl->ncol; i++)
{
if (strlen(ptbl->cols[i].name) > MXCOLNAME)
{
rtastat.nrtaerr++;
if (rtadbg.rtaerr)
rtalog(LOC, Er_Cname_Big, ptbl->cols[i].name);
return (RTA_ERROR);
}
if (strlen(ptbl->cols[i].help) > MXHELPSTR)
{
rtastat.nrtaerr++;
if (rtadbg.rtaerr)
rtalog(LOC, Er_Hname_Big, ptbl->cols[i].name);
return (RTA_ERROR);
}
if (ptbl->cols[i].type > MXCOLTYPE)
{
rtastat.nrtaerr++;
if (rtadbg.rtaerr)
rtalog(LOC, Er_Col_Type, ptbl->cols[i].name);
return (RTA_ERROR);
}
if (ptbl->cols[i].flags > RTA_DISKSAVE + RTA_READONLY)
{
rtastat.nrtaerr++;
if (rtadbg.rtaerr)
rtalog(LOC, Er_Col_Flag, ptbl->cols[i].name);
return (RTA_ERROR);
}
}
/* Verify that we can add the columns */
if ((Ncol + ptbl->ncol) >= MX_COL)
{
rtastat.nrtaerr++;
if (rtadbg.rtaerr)
rtalog(LOC, Er_Max_Cols);
return (RTA_ERROR);
}
/* Everything looks OK. Add table and columns */
Tbl[Ntbl++] = ptbl;
Tbl[0]->nrows = Ntbl;
/* Add columns to list of column pointers */
for (i = 0; i < ptbl->ncol; i++)
{
Col[Ncol++] = &(ptbl->cols[i]);
}
rta_columnsTable.nrows += ptbl->ncol;
/* Execute commands in the save file to restore */
if (ptbl->savefile && strlen(ptbl->savefile) > 0)
(void) rta_load(ptbl, ptbl->savefile);
return (RTA_SUCCESS);
}
/***************************************************************
* Postgres "packets" are identified by their first few bytes.
* The newer protocol used a single ASCII byte to identify the
* packet type, while the older protocol has a 32 bit length
* field at the start of the packet. Note that multi-byte data
* is sent with the most significant byte first. Please see the
* full documentation in "PostgreSQL 7.2.1 Developer's Guide"
* at http://www.postgresql.org/idocs/
*
* The Postgres protocol from client to server has about six
* request types. We use three of the request types in our
* basic implementation. The six packet types.....
* BYTE0 BYTE1 BYTE2 BYTE3
* 0 0 0x1 0x18 Startup packet to open connection
* 0 ? ? ? Encrypted password packet
* 0 0 0 0x10 Cancel pending request
* 'F' Function call
* 'Q' Query
* 'X' Terminate connection
*
**************************************************************/
/***************************************************************
* dbcommand(): - Depacketize and execute any Postgres
* commands in the input buffer.
*
* Input: buf - the buffer with the Postgres packet
* nin - on entry, the number of bytes in 'buf',
* on exit, the number of bytes remaining in buf
* out - the buffer to hold responses back to client
* nout - on entry, the number of free bytes in 'out'
* on exit, the number of remaining free bytes
* Return: RTA_SUCCESS - executed one command
* RTA_NOCMD - input did not have a full cmd
* RTA_ERROR - some kind of error
* RTA_CLOSE - client requests a orderly close
**************************************************************/
int
dbcommand(char *buf, int *nin, char *out, int *nout, int connid)
{
extern struct EpgStat rtastat;
int length; /* lenght of the packet if old protocol */
int i; /* a temp integer */
lnode_t *lnode;
EpgConn *conn;
char line[MX_LN_SZ]; /* input line from file */
char reply[MX_LN_SZ]; /* response from SQL process */
int nreply; /* number of free bytes in reply */
/* old style packet if first byte is zero */
if ((int) buf[0] == 0)
{
/* get length. Enough bytes for a length? if not, consume no
input, write no output */
if (*nin < 4)
{
return (RTA_NOCMD);
}
length = (int) (buf[3] + (buf[2] << 8) + (buf[1] << 16));
/* Is the whole packet here? If not, consume no input, write no
output */
if (*nin < length)
{
return (RTA_NOCMD);
}
if (length == 296) /* a startup request */
{
/* we key on a non-null user name to send AuthOK. The protocol
packet has an int32 for the length, an int32 for the protocol
version, a 64 char string for the DB name and at byte 72 the
start of a 32 char user name. */
if (buf[72] == (char) 0)
{
*nin -= length;
out[0] = 'N'; /* "Notice" response */
*nout -= 1;
rtastat.nauth++;
}
else
{
/* first thing we do is find the hash for this struct? */
conn = getconndata(connid);
if (conn)
{
snprintf(conn->cmd, 1000, "Re-Authenticating");
snprintf((char *) conn->username, 32, "%s", &buf[72]);
conn->rsp[0] = '\0';
}
else
{
conn = malloc(sizeof(EpgConn));
bzero(conn, sizeof(EpgConn));
snprintf(conn->cmd, 1000, "Authenticating");
snprintf((char *) conn->username, 32, "%s", &buf[72]);
conn->id = connid;
conn->ctm = time(NULL);
lnode = lnode_create(conn);
list_append(pgconn, lnode);
}
*nin -= length;
out[0] = 'R';
out[1] = 0;
out[2] = 0;
out[3] = 0;
out[4] = 3;
// out[5] = 'Z';
// *out++ = 'R';
// ad_int4 (&buf, 0);
// *out++ = 'Z';
*nout -= 5;
}
return (RTA_SUCCESS);
}
else if (length == 16) /* a cancel request */
{
/* ignore the request for now */
conn = getconndata(connid);
if (conn)
{
snprintf((char *) conn->cmd, 1000, "Cancel Call");
conn->rsp[0] = '\0';
}
*nin -= length;
return (RTA_SUCCESS);
}
else /* should be a password */
{
conn = getconndata(connid);
if (!conn)
{
/* pass before username (and thus conn?) Bah */
return (RTA_CLOSE);
}
snprintf((char *) conn->password, 32, "%s", &buf[4]);
nreply = MX_LN_SZ;
snprintf(line, MX_LN_SZ,
"select * from pg_user where usename=\"%s\" and passwd = \"%s\"",
conn->username, conn->password);
SQL_string(line, reply, &nreply);
if (nreply != 1279)
{
/* SQL command failed! Report error */
rtastat.nsyserr++;
if (rtadbg.syserr)
rtalog(LOC, Er_BadPass);
*nin -= length;
out[0] = 'E';
out[1] = 'B';
out[2] = 'A';
out[3] = 'D';
out[4] = ' ';
out[5] = 'U';
out[6] = 'S';
out[7] = 'E';
out[8] = 'R';
out[9] = '/';
out[10] = 'P';
out[11] = 'A';
out[12] = 'S';
out[13] = 'S';
out[14] = 0;
*nout -= 15;
return (RTA_CLOSE);
}
/* XXX validate it */
*nin -= length;
out[0] = 'R';
out[1] = 0;
out[2] = 0;
out[3] = 0;
out[4] = 0;
out[5] = 'Z';
*nout -= 6;
return (RTA_SUCCESS);
}
}
else if (buf[0] == 'Q') /* a query request */
{
conn = getconndata(connid);
/* check for a complete command */
for (i = 0; i < *nin; i++)
{
if (buf[i] == (char) 0)
break;
}
if (i == *nin)
{
if (conn)
snprintf(conn->cmd, 50000, "Reading Query");
return (RTA_NOCMD);
}
/* Got a null terminated command; do it. (buf[1] since the SQL
follows the 'Q') */
if (!conn)
{
return (RTA_CLOSE);
}
snprintf((char *) conn->cmd, 1000, "Query: %s", &buf[1]);
SQL_string(&buf[1], out, nout);
snprintf((char *) conn->rsp, 50000, "%s", out);
*nin -= strlen(buf); /* to swallow the cmd */
(*nin)--; /* to swallow the null */
return (RTA_SUCCESS);
}
else if (buf[0] == 'X') /* a terminate request */
{
conn = getconndata(connid);
if (conn)
snprintf(conn->cmd, 1000, "Disconnecting");
return (RTA_CLOSE);
}
else if (buf[0] == 'F') /* a function request */
{
conn = getconndata(connid);
if (conn)
snprintf(conn->cmd, 1000,
"Unsupported Function Call. Disconnecting");
return (RTA_CLOSE);
}
conn = getconndata(connid);
if (conn)
snprintf(conn->cmd, 1000, "Unsupported Call. Disconnecting");
/* an unknown request (should be logged?) */
return (RTA_CLOSE);
}
void
deldbconnection(int connid)
{
lnode_t *lnode;
EpgConn *conn;
lnode = list_first(pgconn);
while (lnode)
{
conn = lnode_get(lnode);
if (conn->id == connid) {
list_delete(pgconn, lnode);
lnode_destroy(lnode);
free(conn);
return;
}
lnode = list_next(pgconn, lnode);
}
}
/***************************************************************
* rta_save(): - Save a table to file. The save format is a
* series of UPDATE commands saved in the file specified. The
* file is typically read in later and executed one line at a
* time.
*
* Input: ptbl - pointer to the table to be saved
* fname - string with name of the save file
*
* Return: RTA_SUCCESS - table saved
* RTA_ERROR - some kind of error
**************************************************************/
int
rta_save(TBLDEF *ptbl, char *fname)
{
extern struct EpgStat rtastat;
int sr; /* the Size of each Row in the table */
int rx; /* Row indeX in for() loop */
void *pd; /* Pointer to the Data in the table/column */
int cx; /* Column index while building Data pkt */
char tfile[PATH_MAX];
char path[PATH_MAX];
int fd; /* file descriptor of temp file */
FILE *ftmp; /* FILE handle to the temp file */
int did_header; /* == 1 if printed UPDATE part */
int did_1_col; /* == 1 if at least one col printed */
/* Open a temp file in the same directory as the users target file */
(void) strncpy(path, fname, PATH_MAX);
(void) strncpy(tfile, dirname(path), PATH_MAX);
(void) strcat(tfile, "/tmpXXXXXX");
fd = mkstemp(tfile);
if (fd < 0)
{
rtastat.nsyserr++;
if (rtadbg.syserr)
rtalog(LOC, Er_No_Save, tfile);
return (RTA_ERROR);
}
ftmp = fdopen(fd, "w");
if (ftmp == (FILE *) 0)
{
rtastat.nsyserr++;
if (rtadbg.syserr)
rtalog(LOC, Er_No_Save, tfile);
return (RTA_ERROR);
}
/* OK, temp file is open and ready to receive table data */
sr = ptbl->rowlen;
for (rx = 0; rx < ptbl->nrows; rx++)
{
did_header = 0;
did_1_col = 0;
for (cx = 0; cx < ptbl->ncol; cx++)
{
if (!ptbl->cols[cx].flags & RTA_DISKSAVE)
continue;
if (!did_header)
{
fprintf(ftmp, "UPDATE %s SET", ptbl->name);
did_header = 1;
}
if (!did_1_col)
fprintf(ftmp, " %s ", ptbl->cols[cx].name);
else
fprintf(ftmp, ", %s ", ptbl->cols[cx].name);
/* compute pointer to actual data */
pd = ptbl->address + (rx * sr) + ptbl->cols[cx].offset;
switch ((ptbl->cols[cx]).type)
{
case RTA_STR:
if (memchr((char *) pd, '"', ptbl->cols[cx].length))
fprintf(ftmp, "= \'%s\'", (char *) pd);
else
fprintf(ftmp, "= \"%s\"", (char *) pd);
break;
case RTA_PSTR:
if (memchr((char *) pd, '"', ptbl->cols[cx].length))
fprintf(ftmp, "= \'%s\'", *(char **) pd);
else
fprintf(ftmp, "= \"%s\"", *(char **) pd);
break;
case RTA_INT:
fprintf(ftmp, "= %d", *((int *) pd));
break;
case RTA_PINT:
fprintf(ftmp, "= %d", **((int **) pd));
break;
case RTA_LONG:
fprintf(ftmp, "= %lld", *((long long *) pd));
break;
case RTA_PLONG:
fprintf(ftmp, "= %lld", **((long long **) pd));
break;
case RTA_PTR:
/* works only if INT and PTR are same size */
fprintf(ftmp, "= %d", *((int *) pd));
break;
case RTA_FLOAT:
fprintf(ftmp, "= %20.10f", *((float *) pd));
break;
case RTA_PFLOAT:
fprintf(ftmp, "= %20.10f", **((float **) pd));
break;
}
did_1_col = 1;
}
if (did_header)
fprintf(ftmp, " LIMIT 1 OFFSET %d\n", rx);
}
/* Done saving the data. Close the file and rename it to the
location the user requested */
/* (BTW: we use rename() because it is guaranteed to be atomic.
Rename() requires that both files be on the same partition; hence
our effort to put the temp file in the same directory as the
target file.) */
(void) fclose(ftmp);
if (rename(tfile, fname) != 0)
{
rtastat.nsyserr++;
if (rtadbg.syserr)
rtalog(LOC, Er_No_Save, fname);
return (RTA_ERROR);
}
return (RTA_SUCCESS);
}
/***************************************************************
* rta_load(): - Load a table from a file of UPDATE commands.
*
* Input: ptbl - pointer to the table to be loaded
* fname - string with name of the load file
*
* Return: RTA_SUCCESS - table loaded
* RTA_ERROR - some kind of error
**************************************************************/
int
rta_load(TBLDEF *ptbl, char *fname)
{
extern struct EpgStat rtastat;
FILE *fp; /* FILE handle to the load file */
char *savefilename; /* table's savefile name */
char line[MX_LN_SZ]; /* input line from file */
char reply[MX_LN_SZ]; /* response from SQL process */
int nreply; /* number of free bytes in reply */
/* We open the load file and read it one line at a time, executing
each line that contains "UPDATE" as the first word. (Lines not
starting with UPDATE are comments.) Note that any write callbacks
associated with the table will be invoked. We hide the table's
save file name, if any, in order to prevent the system from trying
to save the table before we are done loading it. */
fp = fopen(fname, "r");
if (fp == (FILE *) 0)
{
rtastat.nsyserr++;
if (rtadbg.syserr)
rtalog(LOC, Er_No_Load, fname);
return (RTA_ERROR);
}
/* Don't let the DB try to save changes right now */
savefilename = ptbl->savefile;
ptbl->savefile = (char *) 0;
/* process each line in the file */
while (fgets(line, MX_LN_SZ, fp))
{
/* A comment if first word is not "UPDATE " */
if (strncmp(line, "UPDATE ", 7))
continue;
nreply = MX_LN_SZ;
SQL_string(line, reply, &nreply);
if (!strncmp(line, "UPDATE 1", 8))
{
/* SQL command failed! Report error */
rtastat.nsyserr++;
if (rtadbg.syserr)
rtalog(LOC, Er_No_Load, fname);
return (RTA_ERROR);
}
}
ptbl->savefile = savefilename;
return (RTA_SUCCESS);
}