SVN-fs-dump-format-version: 2 UUID: df4edbe4-c1cf-4b3c-a98b-32b18523655e Revision-number: 0 Prop-content-length: 56 Content-length: 56 K 8 svn:date V 27 2008-07-26T05:08:00.707151Z PROPS-END Revision-number: 1 Prop-content-length: 115 Content-length: 115 K 7 svn:log V 14 initial import K 10 svn:author V 6 dneiss K 8 svn:date V 27 2008-07-26T05:09:02.031830Z PROPS-END Node-path: COPYING Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 26520 Text-content-md5: 373b100f832abfd93aa7faf0fa087874 Content-length: 26530 PROPS-END GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! Node-path: ChangeLog Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 6467 Text-content-md5: 8a8c741bf84291be022cfd77085433ac Content-length: 6477 PROPS-END 2006-04-06 Bob Smith Version 0.7.5 - Removed support for FUSE - Makefile changes include removing efence, making librtadb the default target and adding an 'uninstall' target - SQL_string() has a new calling parameter which has the length of input SQL string - The web/PHP table editor now supports multiple RTA apps, and supports strings with embedded single quotes. - Fixed a bug in the table editor in which you could not edit tables with more than 20 rows. - Switched from byacc to Bison - SQL statements must now be terminated by either \0 or ';' - Fixed bug in which an extra "'" at end of line was not flagged as an error. e.g.: psql -h localhost -p 8888 -c "update mytable set myint=6 '" - We now limit the length of a returned string to the length as defined in the column definition 2004-11-14 Bob Smith Version 0.7.4 - Read and write callbacks now return a zero on success and a non-zero on failure. A failed write callback restores the table row to its initial values. No attempt is made to undo other side effects of callbacks so some care should be taken. - Minor web site documentation clean up. Got fuse to run on the demo web server so a link to the fuse-mapped app directory is back in the "livedemo" page. 2004-07-26 Bob Smith Version 1.0.1/0.7.3 - Added support for the new psql client protocol in which the very first packet from the client is a request for an SSL encrypted session. We reply with an 'N' to indicate that SSL sessions are not yet supported. Modified api.c. - Fixed a bug in the SQL parser by running flex and yacc under Linux (Mdk9.2) and saving the resultant .c file. Modified parse.c and parse.h. (1.0.1 only. 0.7.3 still uses parse.y as the input file.) - Added 0.7.3 which is a pure Makefile based version of RTA. It has the same code base as 1.0.1 but does not use automake. - Removed empd from the distribution. 2004-04-20 Graham Phillips Version 1.0.0. Added autoconf support. Ported rta to Windows. User may run './configure' and 'make' on Linux and Windows platforms. Added strndup(), dirname() and mkstemp() for platforms that don't have these methods. 2004-03-07 Bob Smith Version 0.7.2. - Added a prototype daemon based on RTA. The daemon is called 'empty daemon' or empd. This addition prompted the addition of rta_config_dir below. - Fixed a bug in the iterator for the rta_tables table. - Fixed various typo and spelling errors. - Added the 'rta_config_dir' to specify a directory to be prepended to table savefile names. If the savefile uses an absolute path (starting with '/') it is not prepended with the configuration directory. - Added checks to prevent the use of our reserved SQL words as either a table name or as a column name. Reserved words are: SELECT, UPDATE, FROM, WHERE, LIMIT, OFFSET, and SET. (The words are reserved in both upper and lower case.) Changed the column name 'offset' in rta_columns to 'noff'. 2004-02-26 Bob Smith Version 0.7.1. - Replace "epg" with "rta" in all documents. - Verify that the name in a column definition matches the name of the table it is defined with. - Fixed a bug in which write callbacks were always passed a column name of NULL. - Removed rta_init() from API. It is not needed and it's safer to do the init on the first call to rta_add_table(). - Added another parameter to the write callback which points to a copy of the unmodified row. The malloc(), memcpy(), and free() slows the system slightly but the ability to do edge detection is worth the extra CPU time. - The length of a string field now includes space for a null at the end of the string. This bug could be a source of a buffer overflow. - Fixed a bug in selecting which columns to save to disk for non-volatile tables. 2004-01-03 Bob Smith Version 0.7.0. - Added a callback function, called an 'iterator', which advances a row pointer from one row to the next. This makes it possible to have linked-lists appear as tables in Run Time Access. Previous versions required that all data be arranged as an array of struct. - Minor bug fix in the table save function. - Rolled shared object version to '.2'. - Modified the table read and write callbacks to add an additional parameter. Be sure to update your callbacks. 2003-12-16 Bob Smith Version 0.6.2. - Converted from PostgreSQL 7.3 protocol to PostgreSQL 7.4 protocol. Version 0.6.2 is *not* compatible with the libraries or tools of PostgreSQL 7.3. Changes include removing the BEGIN and COMMIT commands and removing the pg_user table since these are no longer required for connection set up. The function command is also removed. - dbcommand() now return RTA_NOBUF if there insufficient room in the output buffer for even an error message. 2003-08-26 Bob Smith Version 0.6.1. - Change st_atim to st_atime in rtafs.c. 2003-08-22 Bob Smith Version 0.6.0. - Added virtual file system interface using the fuse package by Miklos Szeredi. The library librtafs.a adds two API routines, rtafs_init() and do_rtafs(), which mount and maintain the program's tables as files in a file system. - This version also fixes a memory leak which occurred when the Yacc parser detected an SQL syntax error. The amount of leaked memory was about equal to the lengths of the table and columns names in the query. 2003-04-18 Bob Smith Version 0.5.1. - Added BEGIN and COMMIT to the SQL commands recognized in order to allow login from Postgres 7.3.X clients. The 7.3 Postgres login uses a transaction as part of the login. The commands, while not causing an error, DO NOTHING. There is no attempt in the code to implement transactions. 2003-02-19 Bob Smith Version 0.5.0. - Initial release. Node-path: README Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 1081 Text-content-md5: 03757376315b1d372a0a23fedde23288 Content-length: 1091 PROPS-END RTA gives you run time access to the data in your program. It is intended for embedded system developers and can greatly simplify user-interface programs by separating the daemon proper from the UI programs. RTA is not a stand-alone database server, but a library which you build into your program and to make your program's internal arrays and data structures look like tables in a database. It uses a subset of the Postgres protocol and is compatible with the Postgres bindings for "C", PHP, and the Postgres command line tool, psql. This release is compatible with the PostgreSQL 8.1, 8.0, and 7.4 protocols. It is *not* compatible with the earlier 7.3 and 7.2 protocols. Please see http://www.runtimeaccess.com for more information and a working example. /*************************************************************** * Run Time Access * Copyright (C) 2003-2006 Robert W Smith (bsmith@linuxtoys.org) * * This program is distributed under the terms of the GNU LGPL. * See the file COPYING file. **************************************************************/ Node-path: doc Node-kind: dir Node-action: add Prop-content-length: 10 Content-length: 10 PROPS-END Node-path: doc/BadUnixModel.png Node-kind: file Node-action: add Prop-content-length: 59 Text-content-length: 6824 Text-content-md5: 7ea51dd6854f2ad03d404616dd8fc838 Content-length: 6883 K 13 svn:mime-type V 24 application/octet-stream PROPS-END PNG  IHDR}QsBITO`IDATx]l5#{];EJ8MP \(82Q *Q.J[V[% 5jU*(q)!PPtŀ\7lY/&fgw̾s=/眙^p8l8<Rk0 g Eb R$&H Eb R$&H Eb R$&H Eb R$&H Eb R$&H Eb R$&H Eb R$&H Eb R$&H Eb R$&H Eb R$&H Eb R$&H Eb R$&H EbqU@&pGƷkVHLWxQ%Ʉ2xk%eБQqMtA2w$s;ٴoD6x7<Ĕ,kR?C6ԁrf2턓eUBGE_zuppphhhpp0 NMMYf/X[=d;֭[ofggg}v̙7oڽ|UUUuuu 8sr]@ 55x<=ܞ={̶$&ǎ۸qȈmll^[[CѣG?{ݺuj?'CWUUѣG .^|'Cِ|yy>s=rȪU^ud[$&>#Tggg0t_.\}2ݗ @[lR/Xbӧ3Ç+++oܸ\GEb9/??_)UVVv̙~j.]JW{쩮nllvZaaRj̙j?7?Cl۶mٯjWW?*,,|'OUTT<^ER0:wܩS>ÁmUQQΝ;ܹ/g`رc~w",XX'oݺ544tH>F=䓛6mjllzo{d6Bb8pӧO===W\z{g˖-[dI]]ҥKmgˑc^zKz{{p jОQXXXTT=cΜ9c"$&"|;]xyYCb R$&H Eb R$&H Eb R$&H Eb R$&H Eb R$&HCawg$&H Eb 似];?(8F(կ~?qrrR)URRm۶}{<ȼyn/|ǯ61cۛ^w[G ۷ҥK {%%%sݻw  xΝ_W#2'ޒ~Ÿl*]g>G2I{uuuYtyƍow_Cu*jLˤ:V4ƴ<OssRhdVcL3>>qƥKfyLTx_6oU~+n㒖1w45`Qkvq9O;`Jt«٘e6lػwod~V>44ա;w4վKdxT~&L.Zz뇇_ymI̬|ll^SJڵ+]:L3t)LrVs*-DG%δԯ~*ט۷_f_Zj͚5[NҬ<~57d3i-~R48^Ʒ/qllw|bbb݊P:ӸҸS&Zh 3[oc3kGdpA^t17QencSkrJ{__7lؐ:HjH /"ht1[/UE',9^cIlr>^*7Hq̚~'&&^~Ȭ< j77we꺻W&j(뗣R#r/.Y$G _y{ٳ^/ӎCE©\gѢE[n ڍ`0^)cXjL9rrr2 ͞={ll 5&^?CRJ崨1Xhgsν|̙3}i5ar!j젤DKYf=V+/ҦME|$&U}}}'Oz._|eG͛7[=HL6mz饗RJغuQJ^{СCV X`޼yVB)FGGKKKRJY=i^9j6˜@b R$&H Eb R$&H Eb R]9`_-Ѩ1*Bb9#IY9Zo#!?UQ7O3F 䌘1[mxV@aL~3RQcgPMxT,ѾXwW艶cŬr 8M4&m2t!1$ -47dbMH_K;*>&`_1^btcÂ21@bH?'d4f Eb ]g}u6?cFrԘ}?( NҨ1\b$Dc}iӾ9J7g1yre]w;0{u[~4oFo7ː zq+AQ)󐘀}M[uO4 5s`' C2תQe5&`ko$z[_iv7wڵ9K˚&œJ閙]`g@)HL"1@)HL"1@)HLI:\t!L,g(`37lUI544(N8a@… ͛g(biO$1WӂvxHL"1@)HL"1@)HL"1@)HL"1@)HL"1@)HL"1@)HL"1@)HL"1@)HL"1@)HL"1@)HL"1@)HL"1@)HL;VWWwuuuǎr ;::%ttt55x{т .TTTd#s>x⁁Լy2ב=+Μ9/^x.]JW{쩮nllzjaaRj֬Yj?W7?hon۶mٯjww?*,,|'OUTT,\PУ`0uܹSN}ZQ***ڹs۷s٠#1|;vO~;mmmmmmmmmmϟ_\\}=//|ׯG1'ljjz駽^o~m7555558pwWWӧ?󞞞+W\vMHYYي+-[dɒK#1G}GF `AAAaaaQQ$9s搏qTTTci{ Eb R$&H Eb R$&H Eb R$&H Eb R$&H Eb R$&H Eb R$&! 8A^^Rjhh(;ii 85kR5ҩ?3+W\n]{{{(JoP}ڵ+W}gۅ}u#[;) B/w=99*))ٶm۪U͛t}}}@?~׵3o ۷ҥK {%%%|7o m``ɓ'Np^xa֭~ߊ 8V8~@ Oƒnjuuu RTA: Embedded DB API

Run Time Access

Frequently Asked Questions

General questions

Building, modifying, and understanding RTA


General questions

What is RTA?
RTA is a memory resident database and virtual file system interface. It is not a stand-alone database server or file system but a library which offers up the program's internal structures and arrays as database tables or as files in a file system. The database interface uses a subset of the Postgres protocol and is compatible with the Postgres bindings for "C", PHP, and the Postgres command line tool, psql.

What is the purpose of RTA?
It provides a means for external programs to view and edit the tables in your running program. This makes debugging easier and means that the user interface parts of your system can be kept apart from the core functionality.

So, is this a database, or what?
No, it is *not* a database. It is an API which lets external programs view and edit your internal data as if the data were in a database or as files in a file system.

Is this like the /proc filesystem?
Yes, in a way. The /proc system lets you view the kernel's internal data as if the data were stored in files in a filesystem. RTA has two libraries, one lets you view your program's internal tables (arrays of structures) as if they were files in a file system, and the other lets you view your arrays of structures as if they were tables in a PostgreSQL database.

Why PostgreSQL?
Mostly because both the design and the documentation of the PostgreSQL protocol are very clean. PostgreSQL offers a simple, well documented protocol to the client. (Our thanks to the designers and tech-writers for PostgreSQL.)

What SQL commands are implemented?
Only two: SELECT and UPDATE, and even then it is only a subset of the usual SELECT and UPDATE. See rta.h for the syntax details of the commands. RTA does not implement transactions.

What SQL/Postgres commands are not implemented?
This is not a database. We do not implement INSERT or DELETE since tables are (for the most part) fixed in size. Nor do we have CREATE TABLE, ALTER TABLE, or DROP TABLE.

The command line tool for PostgreSQL, psql, will connect to a program with RTA, but none of the psql backslash commands will work (since most of the backslash commands require a real PostgreSQL database).

Why such a small subset of SQL?
This is not a database; it is an API. SELECT and UPDATE are the minimum needed to read and edit the data in your running program.

What file system commands are in RTA?
The read(), write(), and readdir() commands are implemented as is the stat() function. Minimal stubs are provided for both open() and truncate().

Who should not use RTA?
RTA is great if you have data already arranged as structs or as array of structs. It is a little more difficult to use if you have lots of structs pointing to other structs pointing to other structs. That is, it is a good fit for simple tables of data and is not a good fit for a deeply nested tree structure of data.

It is also best for single process applications where the data is in one memory space. For instance, it might not be a good fit for Apache which forks several equal, independent processes.

How big is it?
The stripped shared-object SQL interface library (librtadb.so.2) is 55 KB and the static (.a) library is 46 KB. The stripped virtual file system interface (librtafs.so.2) is 28KB and the static library is 11KB. Note that to use the virtual file system interface you need to load the SQL interface as well.

Do I need to know SQL to use RTA?
Not really. There are only two commands and they match pretty well with the intuitive idea of "change this column in this table where ...". It is pretty simple.
Also, the librtafs library lets you avoid SQL entirely if you wish.

How difficult is it to learn and use RTA?
It is easy to learn. There are only two important data structures (one to describe a table and one to describe a column) and only seven subroutines in the API. The tutorial elsewhere on this page should give you a pretty good idea of the simplicity of the approach.
It is simple but *may* require some effort on your part. Each table and each column in each table needs to be defined in a data structure. While simple, it can be a fair amount of work.

How do I report bugs?
Please select "Contact Info" from the menu above to open a query form. Please give as much detail as possible.

Where can I get help?
Please select "Contact Info" from the menu above to open a query form. Please give as much detail as possible.

Can I send you money?
Please send bug reports and change requests. Send money to the Free Software Foundation.


Building, modifying, and understanding RTA

What do I need to do to use RTA in my program?
You need to describe each table you wish to make available. A table is an array of structures. Each member of the structure forms a column of the table. The table description includes attributes of the table in general and a description of each column in the table. See the sample application and the API reference for more detail.

What libraries, tools and include files are needed?
We use the following system include files: libgen.h, limits.h, stdarg.h, stddef.h, stdio.h, stdlib.h, string.h, syslog.h, and the following system and utility calls: closelog(), dirname(), fclose(), fdopen(), fgets(), fopen(), fprintf(), free(), malloc(), memchr(), mkstemp(), offsetof(), openlog(), rename(), snprintf(), sprintf(), sscanf(), strcat(), strcmp(), strcpy(), strlen(), strncmp(), strncpy(), strstr(), syslog(), va_arg(), va_end(), va_start(). You will need yacc and lex to build RTA.

Do I need the Postgres libraries?
Your application does not need any libraries or include files from the PostgreSQL distribution. Your *client* applications may need PostgreSQL libraries as appropriate.

Can I link to it statically?
Yes, You can link to the .a file or just include the .c files in the build of your application.

What has it been tested on?
It has been used successfully Red Hat 8.0 and 9.0, and on Mandrake 9.1 and 9.2 systems.

Can I add my own data type?
Yes. You will need to modify the lex program to recognize the new data type as well as the .c files which print or compare its values.

Why are there "magic" numbers in the code?
Several places in the code contain what seem like magic values. These are all related to the PostgreSQL protocol and should all be described by comments in the surrounding code.

Why are the pseudo-tables arrays of pointers?
The short answer is to save memory. If rta_add_table() were to copy the table definition your application would have the definition two places in memory: internal to RTA, and in your application code. By saving the pointers to the table and column definitions we avoid using lots of memory to duplicate data. Saving pointers has the second advantage of letting you change the table definition on the fly if needed.

Why do I get segmentation violations?
We've tried very hard to eliminate memory leaks and segmentation violations from the RTA package but some might have slipped through. A common source of problems is overrunning a string. Be sure to verify that your strings have the proper size specified in the column definitions.

How can I change the size of a table?
Allocate enough memory to store the new, larger table. Copy the old table into the new space. Initialize the new rows added. Copy the address of the new table into the "address" field of the table definition and update the "nrows" field with the new number of rows.

Why use native data type instead of INT4 and INT8?
The idea is to give easy access to *your* data structures. Since most programmers use the native int, long long, and float, we do too.

Why do I get "error while loading shared libraries: librtadb.so.2"?
You need to put the librtadb.so and librtafs.so files into one of the system library directories or you need to something like "export LD_LIBRARY_PATH=../src" in order to let the application find the library.

How big should I make the output buffer for dbcommand()?
It depends on the number of rows and the size and number of fields you request. You should be able to estimate the number of characters in your response. Assume that strings are returned with all bytes filled, that integers return 12 characters per field, longs return 24, and floats return 24. Thus if you request two integers, a 40 byte string and a float, you would need about 40+(2*12)+24=88 bytes. The Postgres overhead per row returned is about 4 bytes. Make the output buffer for dbcommand() big enough to hold the maximum number of rows you want times the size of each row. Be sure to use LIMIT and OFFSET to step through a big list of returned rows.

Why must a write() have all data in a single buffer?
RTA requires a single write buffer in order to be sure that all table writes are atomic. Without atomic table writes we could not guarantee table consistency.

Can I export a simple variable?
Sure. Just define a single column for your variable and a table with a single row. You can make visible simple variables, single structures, or arrays of structures.

Is RTA thread safe?
RTA is not currently thread safe. Please contact the authors if this is very high on your wish list.

What is in the To-Do list?
Please let the authors know what features you would like added or removed from Run Time Access. No additional changes are anticipated at this time, but the list of possible changes includes:

  • UTF-8 support
  • printf format string in the column definition
  • ability to register more than one trigger per column
  • secure login maintaining Postgres compatibility
  • IPC and support for shared tables in shared memory
  • specify pre or post for the write callback
  • table save callback (to save file to flash?)
  • execution times for table access, update
  • count(*) function
  • model output buffer mgmt on zlib to allow output streams
  • add a column data type of "table" to allow nested tables
  • make it thread safe

Valid HTML 4.01!

Node-path: doc/GoodrtaModel.png Node-kind: file Node-action: add Prop-content-length: 59 Text-content-length: 16651 Text-content-md5: a2f47f92c26a1588fd42fdf0f8eacdc9 Content-length: 16710 K 13 svn:mime-type V 24 application/octet-stream PROPS-END PNG  IHDRwsBITO IDATxy@TgE}-[̍EJ3ӺU2SV.$ hݼ$̴4˛eY澠"^5sqe6q0̙Gɜ03ysIlVd2 !."eP ) RHYBR(@),J!eP ) RHYBR(@),J!eP ) RHYBR(%@L&fd2fvnȳ/ly${A—Intz VhU, FAçY +ls=5vgJCYQ%5dWn9l|۷])a= .i˔$dۿL̀GH/0^dQŦ|}•tR>Rqb#WvRϕGo|ʧpc׺)fNA Q*nUx_]+odgDG9}?8~xnncǬ/O} 9P%%l6޽;""B=yXtiaaan"##W߄ ~i(w 0HY@rss?ӧ>Ç1>Lc@׸qc!իW6l|E%rڵqƅ޸qCѰaC% ) KܤI׬Y3:::++˥'zUf͔|i8߿; x/_駟fddddd!6m6.\`}WPA"c#e_TZ5U\`c RHYBR=J"eP ) RHYBRf,J!eP ) RHYBR(@),J!eP ) RHYBRu]B#GxpҁGףG!DVVԩS ;PAAԩSL2eʔyaʊڸJcw9KYBF'D,4R],4R],4R]wR]'ڲrHYwc (|) (|) (|WF,4R],4R],4R]\P) .J(|=ƀHYwHYwHYwqU @i,hJS*e322LP@FFB2 RPmYwҔM٨(3<$**J?|) (,໘(|mY@i,HY@i,HY@i,F,4R],4R],4R],4R]~F,4R], Pdl6ʶŮ) (mY)#mcc\PS֚uC,4#ۯK]#eDʚL&i\>)_A~)w,4_1v&8mfH;ۯKRqY@9>іAp—Ih1x[ֺaَJmqc| 6x, ;e~d\e6CCC;utAǝI)SxD|:e_Aka=f) 2L:t(((HNN~-Z /xJ63`]ls- "zdɒ{Zt8.;u۷oӧQFޮ0㧬1\|jW{ RTTdyоԩS󋏏WJp|}^>}:++K1lذUV̿˗/vАcΟ?ʕ۷ooٲpӧOqFϞ=4iZܺuM69rȑ#le2j֬tN:)]!{y7N:nܸZإgϞm2,0RV75ki&ˏM4yWUvK.]t˿oܸq̙2eʨX64%&&&##cտk۶mmf?͜93//k׮ZRL8HYݐ=VZ5;;;..n&MUMf3gTJ]5j1bĉX~|?O!DbbU'#juMUյkW\J#F̛7/88xƌ+LڵkzNho]j~=ƳfͺtRNڵkvq֧ʸqڌ')+BvҥiӦ޽˗~M]VUT=z"11ђ׮]1c! x)#Z8kޅ իWg~wk 6f͚s9w\۶mx  E)k骵ﶵ_䰭lב_*h[yZ=ztaaoݺյkݻwO6RJ_~e&M^ƞ#*T'XXX8m4ACPSVj>:춵gvvX#ߏ-ߓ\xwBCCۗ"([Qrss^őr_#\Z]N;w !n޼yɖ-[vUzJN 7-%-nT%\rKKK>(kמ?Mڶm{^{]v7o.΅7 R|:j"CjSPPXi\BB,nS]Umj׮[o/_-ifƍYYYuԑwNr(θՄլAխ[Wa6ß{9+ )kZا4iRw1uTw2e,\0$$dڴi<›ʖ-;n8~~4d_JgBDEE2ȫ~9 %mIr(**J1{lL{:grpp_ʱd +3WB/TcʇjT^m&tOwN5;AlKeZ 5Mrz'|֭_^B |k׮ ޽{sss>+<ީ2_:~?nV~s׫rCzz}ݷiӦ:;w޵k׌3Tzꈈ1c\rvƃ>=7CrFrAQQ۷ذ4STr9s!:SL#F<`ڴi!!! .,***NM*i$吲FЭ[K߿5k֬9w-[oԩS|Go"^}f>65}B-[ܰa|}mٲ]vڟiYfLfsszW#? Q&_IHH MKKup4eڞå hy!e}]wݕo߾={^z5..I&_~uA[bźuåk2MtA#ROܹ߯Zڵk5ko_|Y] x$<^C[QQQEEE3f7o^IQݙTۂu,lըQc[nСÙ3gCmܸQ0\ ${0/“M,k޼?tx`۶m=?'NUvr֍찷V=dG:ۿUi) L&S߾}ߟT\ŋL4͛j胥9!+sk͏#nZpnKz\~S eQ'''߿^vZbbbxxʕ+\))EjnΉW6u*-MqݨJMٌ <$##C?u~ׯoڴÇzj޽*6 m>yTMU,Jcǎ۷o5kV׭[׼yÇ_x [ ЦbGf;\4JStbh ̙SPPPFA+wDlqȱ~~~T׋ޕq]Y~:7?nUjٳgׯ_/h޼̙3;tvQ,&en-ǵYDΎR$) Fd2gԩER{M7nL6-55˗3fLPPk?~<&&O>1>ԩS_xO\kM!e$) Elذa;wBti̙jep, LxXnݚQF׷l7߼pu HYxdddnnaÄf ׿UXX>9_c eݻwĈ֭B4mt̙;vtc?Z z$=nʕ߽{wNz}ђG_W{ mYx͛7O>yk׮3&66|jee$s'N.^l6?}qeUkM!e$) lܸq۶mBta̙͛7DkM!e$Pͣ>yZjmذuΝلqYzDB~~~ 9r_FFFpp?ςKۿȑ#׮]+9sO>i4c@h5I1",,oYjUÆ ?ru@iЊ=zdggVXqʕ7NLLv횴qYzD14?;vG}d6ᄡ^z+ʠ{Mr&4mӦMÆ ۲eGݸq) &IYh]QQѢEO:%=rZj[kO:??8p`e˖B{j.- }ά]z",,ܹEimY@h5I[zz꯿:88x]tyg:v] tmϞ=SNTҪUǎv]=ХSN/ZN:ϞC1 :vw/X_}GW_}GNDFF`ܹj?U:R }J`LW_}eʔY`App{mKCi͙3ǬUC Q>Ҋ+fggʕ+Gnڴ5kY!#ea 6\j7|znݺ=ӹjwueӧO\_s+{, L2#G4hPAAԩSCBB>â"K[HYCjܼy>zԩm~SBGZjO?{7oܶmxgx|&Lطo_^?s HYփ>|>""={ܹ}Ԯ * d\(R踬C:uھ}|PZy#Ft7k 6KؿrZcx) 8o:h̙s-,,T4dV\lG_>ت^YoޱcdzgFFFi槟~RB.iڴ-[Vn;vhw?~|PPҥK&Nx Q,P2AAAsƍgj vx ) }Z<'_Yf_x'|2;;[h ) ߶mٳkԨ߷h⭷޺pu``,AHQQQo>={6g(lu3_]ߜ`7TZ߱cǓO>yCj~P.ͯ_>oI&֭[bEzvթS>}=zTlwɖG;鋆s߾})))wuק~6~ׯ]195eAf.m͎:T\_~ͳ`^ IDATaaa˖-S._Q^fRP}oذe˖ǎ۷oǎwڥv]W^? <-!e}{o~˖-sέY?تUC?^ du.ߒ6LKXe+HY@q~~~1bٳ?pԩ'N(Z~Ny$3,m*U̘1c׮];wp[oբE ׬Ys}EGG{H]'l0[;VZj+-l8Fvp+wDW]/hРAvvO>٫W#GlҬYJ*}嗋/ZZVomگ#y Y6+(= 3IY>3{2eJ VXѸqq]vuywÇ?sw+YڬHHY@qqqׯ߭[RRRBCC,Y;w>wtGzAB 0.P:u6nئM?_СÎ;lV3LsέXeV\J@k۶Mϟ_vM6gϞ^nݺBCrD34SR,`;j(sϜ93??߲NTTTN:oX*|L`S#YjكL0,)*U6mݻvzҥ#F4oo͛7/((hѢEk֬QTקso.1QWP) }0[ZHHի}u;tQF'NBDFF^rEJ4{{IOOT_|7bĈ~㱱j,7-[v̘1߿~~)S}͛8gΜ׫]"##MZ5w\) hw߽p_>qD~"##h6̝k-_wF-;qDŊ+Upl;vɓ'Tr…#GΘ1Cx]l2Px|nT_J4$""";;߿UZ\rk֬/|)(g.&e{FFQٳԮBZ{(r+Vz]g\o@R) ?_.^(3g\|PapgҔM٨ٳg+zv5jԨQ/9A뗥[8[d0e%fӟl}]'enkH}NRT Sh4vڠ- (|o̤,$[,ͶzD>zLBAi2 ?*>aSPP"*hڰ9~ ok0,l>TZS>w]C ) }0Qq&J!e/ @1|5BBF7)ksl8쒕̷oapRDI 9ҷ/(px-#}~>po )@mY!GuZZ?261]mɕr 4`/G[V/q2+X/r6,MUq,V Q- ׏?XXXvJKmYydK44ۖޡC&OGŋ{gW.Ԟ}$/]sUPaرz``h|䭤o˖ܳNܳ} ,00awm!7ם9sroѨQ۷o{(^uOTPh^^{-,,?4LYpaÆ =xw;88ȑ#+Vh֬ǣ5j4g%rGX&!JgWU"ׯ_ƍL&ӠArrrϟߠAo2L={ܱc#"";_[k e- M0G=-7|8l9l[esrr^y̬_~ivk{,[<I`2zse˖5iѣC ?~i򇣇PRV"Yl۩[e6Wk߾}/r&M>cܹs>yωp򴸲!׻w]v-]qG4hPhh… Ԯݤ,$͋XdI@@@tt322֭v]#gϞ%K>|x~ڥpJgNm6!d*((̴xS?o߾ 6:thҋߓ/;FnܗKFXXؾ}|SRVßzChb׮]EEEAAAC 3fLZ,6pAlRo^aԣ w?^p")iĚ o&b:3dȐv%''\r̙111jR4eɓ'>|Xد_?!XU@i~29vQwZUM6vѳg7n׏9{ڥAAo 4hÇ>{+j)S־7D߯?wYf+Vضm۳>{S֯_?..ܹsjM}۷oϙ3'88xȐ!7nx={Lw4N vh?ߺuk=]V^cǞ?^nݚ={vF=t={K+ ZOY48[䰭l96/yKmIlrժU7o޽{^^^jjjz]I;9֭[fjԨСC;lٲݻwOo[Z#־SXײu{Km[ꫯ~]^zu#GM;w]oyM._|Ν{&_ǛֽF}^wdִ?UZz-[zꩱczpq|d25o|Ŋ;wի QsD{ZOcu~GBTwߵnZBSVJ*}aUx@;OYgQ xem(!22ҤsU2>R }(] ) i/ɜ9sZ5d_,LǦG]WhR,="eP mYBBG,JQv\6#####CCG0. 09unDe(5yLARm٨(vd076q_- ŋ.dдqxB|~zׯ߰+WH&''{Y2-]wwQeǎsc)Sx(ʕY؝;7>θ/xʕ;/_3g̞={ݺuU3gHY@[d:u*99y޼ywu1 zZ*Ԕ7~FIGFF>SW\i׮ڥlZ廋ܤq,u5JNN{ӱc?B YYYjwY*$&=0XkHY~^\I&ѧNj۶O?r * :T1cƌ ]#a ;{zv e_bbb6n(+W4͵kNJJZp) `޼yɧNB<쳩k?~ן1cR`V>v؜!D۶m۷op_~%--?++˺c3[ިmH8P/Ҿ}={/_|ƍ"6//W_-,,yG\*<˙o) }0zj߾ƍkժ5k֬^z#G}ڴiW^ 2dmHLL:thj-ְ)_֘j ) (`ǏL!2eJXXXICD#5,AZO!~G{1֕f a/) (_矅5b \V!CXB (e2q,1O0aBfffIçX*h6p~lHYe]&]XBh+_|1v pܱiӦ1c0 ~'6m"bxmY胧emKW!.['j[,|ř3g&N8gM!_,4k׮͘1cԩW\ד^0B*駟NMM W.>1._۷O0JHYoa!D &OܻwoB HY胋=>>~Ĩ(P ) 8{ ,SGb"e2ׯ_1cFzz:Sh ) +,,BbDB[S۴iޱcG*縎1g;v|gנAKoD, - }e<駟5j7)gjCRʖ-[˗>|xlllʕծKŽ kR,NB\R+W8p„ L! ,?|wN8!޽{jjj&MԮ \E14ꫯۻwM6[lP{MJ0#M!ѣ޽{ׯdɒ~M9Cjfd2I+o7C['&&FGGKS\Gt62Kײh<{lJJJFF۷3ޑ(UfMW^ZZt&''w}6im\AU#4g3[niiiL!`$,gCT!k=u:u*Qm},/M!^zbbСC]B\,Z􈔅;w.%%eٖ)ıUTQ.P)Rqtbi q'Lp{@#e322LP@FFB*,,\`Appp||˗vcǎ *SeիWJS[j&ZH9QQQfxHTT,mݺ'޽{vvvz/^eCeÇ'$$|'fz C M!RųBޯT JY{%h6m2eߢE*Ud裏>7n4K.iii͚5SRx*XMu*eYz zCڵkjAAALLc=foݽ{ET*cGJYa)އ|ۏhԨQl֬ل ,n߾=&&?` /Q&EDY_ϝ;\r}4OXBtҢjժǿ:B\,<:CGzqi~ܹA攔OmKggp]iڔ.[xuk;OY>(K9@vWa-v<½>z]gxΧqժJĒ_jjpOU@koZ8HlWFLRקۯ`[M׫{~[BcP?e?A\kV/̽rϘB]?h+0/l2'.B翤G D C, gZPlNg/u]l0=ƀ17WHfx*zABzD_jG,RGtc胳ٰ/!_DC,RFeh=vnՍ{_1(as&}^ݸ=b0 z_#y9̶w-q }VRt|T~6^]lˬO5SqYy@1*6/+ cIDATe-I󄽶itw~= e'+=b\e@1>c @hR(@),J!eP ) RHYBR(@),J!eP ) RHYB$#IENDB` Node-path: doc/apiref.html Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 36107 Text-content-md5: 216d9650868094520015da0e24a0054b Content-length: 36117 PROPS-END RTA: Embedded DB API

Run Time Access

API Specification

- Preamble

RTA is a specialized memory resident database interface. It is not a stand-alone server but a library which attaches to your program and offers up the program's internal structures and arrays as database tables. It uses a subset of the Postgres protocol and is compatible with the Postgres bindings for "C", PHP, and the Postgres command line tool, psql.

This page contains the defines, structures, and function prototypes for the 'RTA' package. The information in this page is taken from the rta.h file in the RTA package. The rta.h file is the authoritative reference to the API.

INDEX:
- Preamble
- Limits
- Data Structures
- Subroutines
- RTA UPDATE and SELECT syntax
- Internal DB tables
- List of all error messages
- How to write callback routines

- Limits

Here are the defines which describe the internal limits set in the RTA package. You are welcome to change these limits; just be sure to recompile the RTA package using your new settings.

#define Value Description
MX_TBL 500 Maximum number of tables allowed in the system. Your database may not contain more than this number of tables.
MX_COL 2500 Maximum number of columns allowed in the system. Your database may not contain more than this number of columns.
MXCOLNAME 30 Maximum number of characters in a column name, table name, help text, and path to the save file. See TBLDEF and COLDEF below.
MXTBLNAME 30
MXHELPSTR 1000
MXFILENAME PATH_MAX
MXDBGIDENT 20 Maximum number of characters in the 'ident' field of the openlog() call. See the rta_dbg table below.
MX_LN_SZ 1500 Maximum line size. SQL commands in save files may contain no more than MX_LN_SZ characters. Lines with more than MX_LN_SZ characters are silently truncated to MX_LN_SZ characters.
NCMDCOLS 40 Maximum number of columns allowed in a table.

- Data Structures

Each column and table in the database must be described in a data structure. Here are the data structures and associated defines to describe tables and columns.

The column definition (COLDEF) structure describes one column of a table. A table description has an array of COLDEFs to describe the columns in the table.

The COLDEF Structure
Type/Name Description
char *table The name of the table that has this column.
char *name The name of the column. Must be at most MXCOLNAME characters in length and must be unique within a table. The same column name may be used in more than one table.
int type The data type of the column. Must be int, long, string, pointer to void, pointer to int, pointer to long, or pointer to string. The DB types are defined immediately following this structure.
int length The number of bytes in the string if the above type is RTA_STR or RTA_PSTR.
int offset Number of bytes from the start of the structure to this column. For example, a structure with an int, a 20 character string, and a long, would have the offset of the long set to 24. Use of the function offsetof() is encouraged. If you have structure members that do not start on word boundaries and you do not want to use offsetof(), then consider using -fpack-struct with gcc.
int flags Boolean flags which describe attributes of the columns. The flags are defined after this structure and include a "read-only" flag and a flag to indicate that updates to this column should cause a table save. (See table savefile described below.)
int (*readcb) () Read callback. This routine is called before the column value is used. Input values include the table name, the column name, the input SQL command, a pointer to the row, and the (zero indexed) row number for the row that is being read. On success, a zero should be returned. This routine is called *each* time the column is read so the following would produce two calls:
SELECT intime FROM inns WHERE intime >= 100;
int (*writecb) () Write callback. This routine is called after an UPDATE in which the column is written. Input values include the table name, the column name, the SQL command, a pointer to the row, the (zero indexed) row number of the the row that changed, and a pointer to the row before any changes were made. See the callback section below. This routine is called only once after all column updates have occurred. For example, if there were a write callback attached to the addr column, the following SQL statement would cause the execution of the write callback once after both mask and addr have been written: UPDATE ethers SET mask="255.255.255.0", addr = "192.168.1.10" WHERE name = "eth1"; The callback is called for each row modified.

If an error is detected during the callback, a non-zero value can be returned. This returns an error to the client and restores the table row to its original value.

char *help A brief description of the column. This should include the meaning of the data in the column, the limits, if any, and the default values. Include a brief description of the side effects of changes. This field is particularly important for tables which are part of the "boundary" between the UI developers and the application programmers.
COLDEF Types and Flags
RTA_STR String refers to an array of char. The 'length' of column must contain the number of bytes in the array.
RTA_PSTR Pointer to string. Pointer to an array of char, or a (**char). Note that the column length should be the number of bytes in the string, not a sizeof(char *).
RTA_INT Integer. This is the compiler/architecture native integer. On Linux/gcc/Pentium an integer is 32 bits.
RTA_PINT Pointer to integer.
RTA_LONG Long. This is the compiler/architecture native long long. On Linux/gcc/Pentium a long long is 64 bits.
RTA_PLONG Pointer to long.
RTA_FLOAT Float. This is the compiler/architecture native float.
RTA_PFLOAT Pointer to float.
RTA_PTR Pointer to void. Use for generic pointers
RTA_DISKSAVE Flag: If the disksave bit is set any writes to the column causes the table to be saved to the "savefile". See savefile described in the TBLDEF section below.
RTA_READONLY Flag: If the readonly flag is set, any writes to the column will fail and a debug log message will be sent. (For unit test you may find it very handy to leave this bit clear to get better test coverage of the corner cases.)


The table definition (TBLDEF) structure describes a table and is passed into the DB system by the rta_add_table() subroutine.

The TBLDEF Structure
Type/Name Description
char *name The name of the table. Must be less than than MXTLBNAME characters in length. Must be unique within the DB.
void *address Address of the first element of the first row of the array of struct that make up the table. This may be set to zero if an iterator is defined for the table.
int rowlen The number of bytes in each row of the table. This is usually a sizeof() of the structure associated with the table. (The idea is that we can get to data element E in row R with ... data = *(address + (R * rowlen) + offset(E))
int nrows Number of rows in the table. This may be set to zero if an iterator is defined for the table.
void *iterator A function which, given a pointer to one row, return a pointer to the next row. The iterator is called as iterator(void *prow, void *it_info, int rowid);
An prow of NULL should return the first row of the table. The function should return NULL when asked for the row after the last row in the table. As a convenience, the row number being requested is also passed. That is, the first call will have rowid equal to zero. You may use either the current row pointer or the requested row number in determining the pointer to the row. The iterator function also passes the it_info void pointer described below.
If an iterator is defined, the TBLDEF values for table address and number of rows are ignored.
The iterator function provides a way to make linked lists and other data arrangements appear as database tables. See the sample application for an example. You might also look at the implementation of rta_tables and rta_columns, since they use an iterator as well.
void *it_info The value of it_info defined in the TBLDEF is passed each time the iterator is called on this table. This lets you define, for example, one iterator function for all of your linked lists. Inside your iterator function you can use it_info to determine which table the iterator should use.
COLDEF *cols An array of COLDEF structures which describe the columns in the table. These must be in statically allocated memory since the RTA system references them while running.
int ncol The number of columns in the table. That is, the number of COLDEFs defined by 'cols'.
char *savefile Save file. Path and name of a file which stores the non-volatile part of the table. The file has all of the UPDATE statements needed to rebuild the table. The file is rewritten in its entirety each time a 'savetodisk' column is updated. No file save is attempted if the savefile is blank.
char *help Help text. A description of the table, how it is used, and what its intent is. A brief note to describe how it relate to other parts of the system and description of important callbacks is nice thing to include here.

- Subroutines

Here is a summary of the few routines in the RTA API:

dbcommand() process data stream from Postgres clients
rta_add_table() add a table and its columns to the DB
SQL_string() execute an SQL statement in the DB
rta_save() save a table to a file
rta_load() load a table from a file
rtafs_init() initialize and mount virtual file system
do_rtafs() process file system commands


dbcommand() - Depacketize and execute Postgres commands
The main application accepts TCP connections from Postgres clients and passes the stream of bytes (encoded SQL requests) from the client into the RTA system via this routine. If the input buffer contains a complete command, it is executed, nin is decrement by the number of bytes consumed, and RTA_SUCCESS is returned. If there is not a complete command, RTA_NOCMD is returned and no bytes are removed from the input buffer. If a command is executed, the results are encoded into the Postgres protocol and placed in the output buffer. When the routine is called the input variable, nout, has the number of free bytes available in the output buffer, out. When the routine returns nout has been decremented by the size of the response placed in the output buffer. An error message is generated if the number of available bytes in the output buffer is too small to hold the response from the SQL command.

int
dbcommand(char *cmd - the buffer with the Postgres packet
          int  *nin - on entry, the number of bytes in 'cmd',
                      on exit, the number of bytes remaining in cmd
          char *out - the buffer to hold responses back to client
          int  *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_CLOSE     - client requests a orderly close
        RTA_NOBUF     - insufficient space in output buffer


rta_add_table() - Add table to DB
Registers a table for inclusion in the DB interface. Adding a table allows external Postgres clients access to the table's content. Note that the TBLDEF structure must be statically allocated. The DB system keeps just the pointer to the table and does not copy the information. This means that you can change the contents of the table definition by changing the contents of the TBLDEF structure. This might be useful if you need to allocate more memory for the table and change its row count and address. An error is returned if another table by the same name already exists in the DB or if the table is defined without any columns. If a 'savefile' is specified, it is loaded. (See the rta_load() command below for more details.)

int
rta_add_table(TBLDEF *ptbl  -pointer to the TBLDEF to add);
Return: RTA_SUCCESS   - table added
        RTA_ERROR     - error


SQL_string()- Execute single SQL command
Executes the SQL command placed in the null-terminated string, cmd. The results are encoded into the Postgres protocol and placed in the output buffer. When the routine is called the input variable, nout, has the number of free bytes available in the output buffer, out. When the routine returns nout has been decremented by the size of the response placed in the output buffer. An error message is generated if the number of available bytes in the output buffer is too small to hold the response from the SQL command.

This routine may be most useful when updating a table value in order to invoke the write callbacks. (The output buffer has the results encoded in the Postgres protocol and might not be too useful directly.)

void
SQL_string(char *cmd - the buffer with the SQL command
           char *out - the buffer to hold responses back to client,
           int *nout - on entry, the number of free bytes in 'out'
                       on exit, the number of remaining free bytes);


rta_save() - Save table to file
Saves all "savetodisk" columns to the path/file specified. Only savetodisk columns are saved. The resultant file is a list of UPDATE commands containing the desired data. There is one UPDATE command for each row in the table.

This routine tries to minimize exposure to corrupted save files by opening a temp file in the same directory as the target file. The data is saved to the temp file and the system call rename() is called to atomically move the temp to the save file. Errors are generated if rta_save can not open the temp file or is unable to rename() it.

As a general warning, note that any disk I/O can cause a program to block briefly and so saving and loading tables might cause your program to block.

int
rta_save(TBLDEF *ptbl - pointer to the TBLDEF structure for the
                        table to save,
         char *fname  - null terminated string with the path and
                        file name for the stored data);
Return: RTA_SUCCESS   - table saved
        RTA_ERROR     - some kind of error


rta_load() - Load table from file
Load a table from a file of UPDATE commands. The file format is a series of UPDATE commands with one command per line. Any write callbacks are executed as the update occurs.

int
rta_load(TBLDEF *ptbl   - pointer to the table to be loaded,
         char *fname  - string with name of the load file);
Return: RTA_SUCCESS   - table loaded
        RTA_ERROR     - could not open the file specified


rta_config_dir() - Set directory prefix for file save/load
The string pointed to by configdir is saved and is prepended to the savefile names for tables with savefiles. This call should be used before loading your application tables. It is intended to make it simpler for applications which let the user specify an configuration directory on the command line.

If the savefile uses an absolute path (ie. one starting with '/') it is not prepended with the configuration directory.

This call is optional. If it is not used the current working directory is used instead.

int
rta_config_dir(char *configdir - path to the configuration files);
Return: RTA_SUCCESS   - config path set
        RTA_ERROR     - error, (not a valid directory?)



- RTA UPDATE and SELECT syntax

RTA IS AN API, *NOT* A DATABASE!

Neither the RTA UPDATE nor the RTA SELECT adhere to the Postgres equivalents. Joins are not allowed, and the WHERE clause supports only the AND relation. There are no locks or transactions.

SELECT:
    SELECT column_list FROM table [where_clause] [limit_clause]

SELECT supports multiple columns, '*', LIMIT, and OFFSET. At most MXCMDCOLS columns can be specified in the select list or in the WHERE clause. LIMIT restricts the number of rows returned to the number specified. OFFSET skips the number of rows specified and begins output with the next row. 'column_list' is a '*' or 'column_name [, column_name ...]'. 'where_clause' is 'col_name = value [AND col_name = value ..]' in which all col=val pairs must match for a row to match.

LIMIT and OFFSET are very useful to prevent a buffer overflow on the output buffer of dbcommand(). They are also very useful for web based user interfaces in which viewing the data a page-at-a-time is desirable.

Column and table names are case sensitive. If a column or table name is one of the reserved words it must be placed in quotes when used. The reserved words are: AND, FROM, LIMIT, OFFSET, SELECT, SET, UPDATE, and WHERE. Reserved words are *not* case sensitive. You may use lower case reserved words in your names.

Comparison operator in the WHERE clause include =, |=, >=, <=, >, and <.

You can use a reserved word, like OFFSET, as a column name but you will need to quote it whenever you reference it in an SQL command (SELECT "offset" FROM tunings ...). Strings may contain any of the !@#$%^&*()_+-={}[]\|:;<>?,./~` characters. If a string contains a double quote, use a single quote to wrap it (eg 'The sign says "Hi mom!"'), and use double quotes to wrap a string with embedded single quotes.

Examples:

 SELECT * FROM rta_tables

 SELECT destIP FROM conns WHERE fd != 0

 SELECT destIP FROM conns WHERE fd != 0 AND lport = 80

 SELECT destIP, destPort FROM conns
       WHERE fd != 0 
       LIMIT 100 OFFSET 0
 
 SELECT destIP, destPort FROM conns
       WHERE fd != 0
       LIMIT 100 OFFSET 0
UPDATE:
    UPDATE table SET update_list [where_clause] [limit_clause]

UPDATE writes values into a table. The update_list is of the form 'col_name = val [, col_name = val ...]. The WHERE and LIMIT clauses are as described above.

An update invokes write callbacks on the affected columns. All data in the row is written before the callbacks are called.

The LIMIT clause for updates is not standard Postgres SQL, but can be really useful for stepping through a table one row at a time. To change only the n'th row of a table, use a limit clause like 'LIMIT 1 OFFSET n' (n is zero-indexed).

Examples:

 UPDATE conn SET lport = 0;

 UPDATE ethers SET mask = "255.255.255.0",  
                   addr = "192.168.1.10"    
               WHERE name = "eth0"

 UPDATE conn SET usecount = 0 WHERE fd != 0 AND lport = 21

- Internal DB tables

The RTA API has five tables visible to the application:

    rta_tables: - a table of all tables in the DB
rta_columns: - a table of all columns in the DB
rta_dbg: - controls what gets logged from rta
egp_stats: - simple usage and error statistics

The rta_tables table gives SQL access to all internal and registered tables. The data in the table is exactly that of the TBLDEF structures registered with rta_add_table(). This table is used for the generic table editor application. The columns of rta_tables are:

    name - the name of the table
address - the start address of the table in memory
rowlen - number of bytes in each row of the table
nrows - number of rows in the table
iterator - callback function to get a pointer to the next row
it_info - transparently passed in each call to the iterator
cols - pointer to array of column definitions
ncol - number of columns in the table
savefile - the file used to store non-volatile columns
help - a description of the table


The rta_columns table has the column definitions of all columns in the DB. The data in the table is exactly that of the COLDEF structures registered with rta_add_table(). This table is used for the generic table editor. The columns of rta_columns are:

    table - the name of the column's table
name - name of the column
type - column's data type
length - number of bytes columns data type
offset - number of bytes from start of structure
flags - Bit field for 'read-only' and 'savetodisk'
readcb - pointer to subroutine called before reads
writecb - pointer to subroutine called after writes
help - a description of the column


The rta_dbgconfig table controls which errors generate debug log messages. See the logging section below for the exact mapping. The RTA package generates no user level log messages, only debug messages. All of the fields in this table are volatile. You will need to set the values in your main program to make them seem persistent. (Try something like "SQL_string("UPDATE rta_dbgconfig SET dbg ....").) The columns of rta_dbgconfig are:

    syserr - integer, 0 means no log, 1 means log. This logs OS call errors like malloc() failures. Default is 1.
rtaerr - integer, 0 means no log, 1 means log. Enables logging of errors internal to the RTA package itself. Default is 1.
sqlerr - integer, 0 means no log, 1 means log. Log any SQL request which generates an error reply. Error replies occur if an SQL request is malformed or if it requests a non-existent table or column. Default is 1. (SQL errors are usually client programming errors.)
trace - integer, 0 means no log, 1 means log all SQL requests. Default is 0.
target - 0: disable all debug logging
1: log debug messages to syslog()
2: log debug messages to stderr
3: log to both syslog() and stderr
The default is 1. Setting the facility causes a close and an open of syslog().
priority - integer. Syslog() requires a priority as part of all log messages. This specifies the priority to use when sending RTA debug messages. Changes to this do not take effect until dbg_target is updated.
0: LOG_EMERG
1: LOG_ALERT
2: LOG_CRIT
3: LOG_ERR
4: LOG_WARNING
5: LOG_NOTICE
6: LOG_INFO
7: LOG_DEBUG
Default is 3.
facility - integer. Syslog() requires a facility as part of all log messages. This specifies the facility to use when sending RTA debug messages. It is best to use the defines in .../sys/syslog.h to set this. The default is LOG_USER. Changes to this do not take effect until dbg_target is updated.
ident - string. Syslog() requires an 'ident' string as part of all log messages. This specifies the ident string to use when sending RTA debug messages. This is normally set to the process or command name. The default is "rta". Changes to this do not take effect until dbg_target is updated. This can be at most MXDBGIDENT characters in length.


The rta_stat table contains usage and error statistics which might be of interest to developers. All fields are of type long, are read-only, and are set to zero the first time a table is added with rta_add_table(). The columns of rta_stats are:

    nsyserr - count of failed OS calls.
nrtaerr - count of internal RTA failures.
nsqlerr - count of SQL failures.
nauth - count of authorizations. (==#connections)
nupdate - count of UPDATE requests
nselect - count of SELECT requests



- List of all error messages

There are two types of error messages available in the RTA package. The first type is the error messages returned in response to a failed SQL request. The messages of this type are:

1) "ERROR: Relation '%s' does not exist"
This reply indicates that a table requested in a SELECT, UPDATE, or WHERE clause does not exist. The %s is replaced by the name of the requested table.
2) "ERROR: Attribute '%s' not found\n"
This reply indicates that a column requested in a SELECT. UPDATE, or WHERE clause does not exist. The %s is replaced by the name of the requested column.
3) "ERROR: SQL parse error"
This reply indicates a mal-formed SQL request or a mis-match in the types of data in a WHERE clause or in an UPDATE list.
4) "ERROR: Output buffer full"
This reply indicates that the size of the response to a request exceeds the size of the output buffer. See dbcommand() and the 'out' and 'nout' parameters. This error can be avoided with a large enough output buffer or, preferably, with the use of LIMIT and OFFSET.
5) "ERROR: String too long for '%s'"
This reply indicates that an UPDATE to a column of type string or pointer to string would have exceeded the width of the column. The %s is replaced by the column name.
6) "ERROR: Can not update read-only column '%s'"
This reply indicates that an attempt to UPDATE a column marked as read-only. The %s is replaced by the column name.
7) "ERROR: Failed callback on column '%s'"
This indicates that a read or write callback (trigger) has has failed. If a write update failed, the table remain unmodified.

The other type of error messages are internal debug messages. Debug messages are logged using the standard syslog() facility available on all Linux systems. The default syslog "facility" used is LOG_USER but this can be changed by setting 'facility' in the rta_dbg table.

You are welcome to modify syslogd in order to do post processing such as generating SNMP traps off these debug messages. All error messages of this type are sent to syslog() as: "rta[PID]: FILE LINE#: error_message", where PID, FILE, and LINE# are replaced by the process ID, the source file name, and the line number where the error was detected.

Following are the defines used to generate these debug and error messages. These are the messages that will appear in the syslog and on the stderr output. The "%s %d" at the start of each error string is replaced by the file name and line number where the error is detected.

         "System" errors 
"%s %d: Can not allocate memory\n"
"%s %d: Table save failure.  Can not open %s\n"
"%s %d: Table load failure.  Can not open %s\n"

         "RTA" errors 
"%s %d: Too many tables in DB\n"
"%s %d: Too many columns in DB\n"
"%s %d: Too many characters in table name: %s\n"
"%s %d: Too many characters in column name: %s\n"
"%s %d: Too many characters in help text: %s\n"
"%s %d: DB already has table named: %s\n"
"%s %d: Table already has column named: %s\n"
"%s %d: Column contains an unknown data type: %s\n"
"%s %d: Column contains unknown flag data: %s\n"
"%s %d: Too many columns in table: %s\n"
"%s %d: Not enough buffer space\n"

         "SQL" errors 
"%s %d: SQL parse error: %s\n"
"%s %d: Attempt to update readonly column: %s\n"

         "Trace" messages 
"%s %d: SQL command: %s  (%s)\n"



- How to write callback routines

As mentioned above, read callbacks are executed before a column value is used and write callbacks are called after all columns have been updated. Both read and write callbacks return an integer which is zero on success and non-zero on failure. Read callbacks have the following calling parameters:

char *tblname   the name of the table referenced
char *colname   the name of the column referenced
char *sqlcmd   the text of the SQL command
void *pr   pointer to the row being read or written
int     rowid   the zero-indexed row number of the row being read or written


Read callbacks are particularly useful to compute things like sums and averages; things that aren't worth the effort to compute continuously if it's possible to compute it just when it is used. A returned value of zero indicates that the read callback was successful.


Write callbacks can form the real engine driving the application. These are most applicable when tied to configuration changes. Write callbacks are also a useful place to log configuration changes.

Write callbacks have the same parameters as read callbacks with the addition of a pointer to a copy of the row before it was modified. Access to a copy of the unmodified row is useful to detect actual changes and not just updates with the same value.

Your write callback can also perform sanity checking on the values about to be entered in the table. If the values are in error, the callback can return a non-zero value. This error is propagated back to the client, and the table row is restored to its original value.

Write callbacks are passed the following parameters:

char *tblname   the name of the table referenced
char *colname   the name of the column referenced
char *sqlcmd   the text of the SQL command
void *pr   pointer to the row being read or written
int     rowid   the zero-indexed row number of the row being read or written
void *poldrow   pointer to a copy of the row before any changes


Valid HTML 4.01!

Node-path: doc/contact.html Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 1762 Text-content-md5: e72563ef23c7f3aa0e4403a47355131c Content-length: 1772 PROPS-END RTA: Embedded DB API

Run Time Access

Contact Us

Please leave us a message in the form below. We look forward to receiving your comments and we reply to all messages. (Be sure to include your email address since all messages using this form come from 'apache'.)

Subject:
Message:

Valid HTML 4.01!

Node-path: doc/contact.php Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 500 Text-content-md5: a513a0abb13f24885f82bd4d7f72f13d Content-length: 510 PROPS-END rta Message Board

rta Message Board

Message sent.       Thanks.\n"); } else { echo("

Message delivery failed...

"); } ?> Node-path: doc/download.html Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 8209 Text-content-md5: be2d6433f7226adfbc9fff86ee4b2150 Content-length: 8219 PROPS-END RTA: Embedded DB API

Run Time Access

Download

Version Change Log
rta-0.7.5.tgz
2006-04-06
- Verified RTA up to version 8.0.3 of the PostgreSQL clients
- Fixed minor bugs in the web based table editor
- Cleaned up and fixed minor bugs in the YACC grammar
- Fixed a bug in the length of returned strings
- Removed support for FUSE
rta-0.7.4.tgz
2004-11-14
- Read and write callbacks now return a zero on success and a non-zero on failure. A failed write callback restores the table row to its initial values. No attempt is made to undo other side effects of callbacks so some care should be taken.
- Minor web site documentation clean up. Got fuse to run on the demo web server so a link to the fuse-mapped app directory is back in the "livedemo" page.
rta-1.0.1.tar.gz
rta-0.7.3.tgz
2004-07-26
- Added support for the new psql client protocol in which the very first packet from the client is a request for an SSL encrypted session. We reply with an 'N' to indicate that SSL sessions are not yet supported. Modified api.c.
- Fixed a bug in the SQL parser by running flex and yacc under Linux (Mdk9.2) and saving the resultant .c file. Modified parse.c and parse.h. (Version 1.0.1 only. Version 0.7.3 still uses parse.y as the input file.)
- Added 0.7.3 which is a pure Makefile based version of RTA. It has the same code base as 1.0.1 but does not use automake.
- Removed empd from the distribution.
rta-1.0.0.tar.gz
2004-04-20
- Added autoconf, thus providing ./configure, make, make install functionality.
- Ported rta to Windows.
rta-0.7.2.tgz 2004-03-07
- Added a prototype deamon based on RTA. The daemon is called 'empty deamon' or empd. This addition prompted the addition of rta_config_dir() below. See the README in the empd directory for more details.
- Fixed a bug in the iterator for the rta_tables table.
- Fixed various typo and spelling errors.
- Added the 'rta_config_dir' to specify a directory to be prepended to table savefile names. If the savefile uses an absolute path (starting with '/') it is not prepended with the configuration directory.
- Added checks to prevent the use of our reserved SQL words as either a table name or as a column name. Reserved words are: SELECT, UPDATE, FROM, WHERE, LIMIT, OFFSET, and SET. (The words are reserved in both upper and lower case.) Changed the column name 'offset' in rta_columns to 'noff'.
rta-0.7.1.tgz 2004-02-26
- Replace "epg" with "rta" in all documents.
- Verify that the name in a column definition matches the name of the table it is defined with.
- Fixed a bug in which write callbacks were always passed a column name of NULL.
- Removed rta_init() from API. It is not needed and it's safer to do the init on the first call to rta_add_table().
- Added another parameter to the write callback which points to a copy of the unmodified row. The malloc(), memcpy(), and free() slows the system slightly but the ability to do edge detection is worth the extra CPU time.
- The length of a string field now includes space for a null at the end of the string. This bug could be a source of a buffer overflow.
- Fixed a bug in selecting which columns to save to disk for non-volatile tables.
rta-0.7.0.tgz 2004-01-03
- Added a callback function, called an 'iterator', which advances a row pointer from one row to the next. This makes it possible to have linked-lists appear as tables in Run Time Access. Previous versions required that all data be arranged as an array of struct.
- Minor bug fix in the table save function.
- Rolled shared object version to '.2'.
- Modified the table read and write callbacks to add an additional parameter. Be sure to update your callbacks.
rta-0.6.2.tgz 2003-12-16
- Converted from PostgreSQL 7.3 protocol to PostgreSQL 7.4 protocol. Version 0.6.2 is *not* compatible with the libraries or tools of PostgreSQL 7.3. Changes include removing the BEGIN and COMMIT commands and removing the pg_user table since these are no longer required for connection set up. The function command is also removed.
- dbcommand() now return RTA_NOBUF if there insufficient room in the output buffer for even an error message.
rta-0.6.1.tgz 2003-08-26
Change st_atim to st_atime in rtafs.c.
rta-0.6.0.tgz 2003-08-22
- Added virtual file system interface using the fuse package by Miklos Szeredi. The library librtafs.a adds two API routines, rtafs_init() and do_rtafs(), which mount and maintain the program's tables as files in a file system.
- This version also fixes a memory leak which occurred when the Yacc parser detected an SQL syntax error. The amount of leaked memory was about equal to the lengths of the table and columns names in the query.
rta-0.5.1.tgz 2003-04-18
- Added BEGIN and COMMIT to the SQL commands recognized in order to allow login from Posgres 7.3.X clients. The 7.3 Postgres login uses a transaction as part of the login. The commands, while not causing an error, DO NOTHING. There is no attempt in the code to implement transactions.
rta-0.5.0.tgz 2003-02-19
Initial release.

Valid HTML 4.01!

Node-path: doc/index.html Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 8405 Text-content-md5: 23cbb2447e63d125a3a54bc5e89cce52 Content-length: 8415 PROPS-END RTA: Embedded DB API

Run Time Access


Introduction

RTA is a library that you can attach to your program to expose your program's internal arrays and data structures as if they were tables in a database. The database interface uses a subset of the Postgres protocol and is compatible with the Postgres bindings for C, PHP, and the Postgres command line tool, psql.


The Problem

One of the problems facing Linux is the lack of run time access to status, statistics, and configuration of a service once the service has started. We assume that to configure an application we will be able to ssh into the box, vi the /etc configuration file, and do a 'kill -1' on the process. Real time status and statistics are things Linux programmers don't even think to ask for. The need for run time access is particularly pronounced for network appliances where ssh is not available or might not be allowed.

The Bad Unix Model

Another problem for appliance designers is that more than one type of user interface may be required. Sometimes a customer requires that no configuration information be sent over an Ethernet line which transports unsecured user data. In such a case the customer may turn off the web interface and require that configuration, status, and statistics be sent over an RS-232 serial line. Other popular interfaces include the VGA console, SNMP MIBs, and LDAP.


The RTA Solution

The RTA package helps solve both of these problems by giving run time access to the data structures and arrays inside your running program. With minimal effort, you make your program's data structures appear as tables in a Postgres database.

The Good RTA Model

For example, say you have a structure for TCP connections defined as:

   struct tcpconn {
     int   fd;       // conn's file descriptor
     int   lport;    // local port number
     int   dport;    // destination port number
     long  nsbytes;  // number of sent bytes
     long  nrbytes;  // number of received bytes
     long  nread;    // number of reads on the socket
     long  nwrite;   // number of writes on the socket
   };

You might then define an array of these structures as:

    struct tcpconn Conns[20];

This array of structs could also be considered a table with seven columns and twenty rows. RTA maps this table into a Postgres database.


A Database Interface

The RTA package gives your program a socket interface like that of a Postgres database. It allows any programming language with a Postgres binding to query your table of TCP connections....

    SELECT lport, dport FROM Conns WHERE fd != -1;
    UPDATE Conns SET dport = 0 WHERE fd = -1;

Only two commands, SELECT and UPDATE, are implemented, but they give you tremendous power to interact with your program. The RTA SQL implementation includes a WHERE clause and both LIMIT and OFFSET. Say you want to allocate one tcpconn by setting the file descriptor to a non-negative number. The UPDATE part of the command would be 'UPDATE Conns SET fd=newfd'. To change only a row where the file descriptor is -1, we add 'WHERE fd=-1'. We want to change only one row so we add 'LIMIT 1'. The final command would be:

    UPDATE Conns SET fd=newfd WHERE fd=-1 LIMIT 1


Advantages

The advantages of separating the user interface programs from the daemon proper fall into the broad categories of design, coding, debug, and capabilities.

From a design point of view, the division forces you to decide early in the design what exactly is offered as part of the UI without worrying how it is displayed. The thought process required to design the tables forces you to think through the real design of your application. The tables might form the internal functional specification of your application.

While coding, the table definitions are what the daemon engineers build to and what the UI engineers build from. The division of UI and daemon means you can hire UI experts and daemon experts independently and they can code independently which might help bring the product to market sooner. Since there are Postgres bindings for PHP, Tcl/Tk, Perl, and "C", your developers can use the right tool for the job.

Debug is faster and easier because both the UI and the daemon engineers can simulate the other half easily. For example, the UI engineers could run their UI programs against a real Postgres DB which has the same tables the daemon will have. Testing the daemon can be easier and more complete since it is easy to build test scripts to simulate the UI, and it is easy to examine internal status and statistics while a test runs. The ability to force an internal state or condition helps test corner cases which are sometimes difficult to do in a lab setup.

The capability of your product can be expanded with RTA. Your customers will really appreciate being able to see detailed status and statistics while the program is running. Separating the UIs from the daemon means you can have a wider variety and a greater number of UI programs. SNMP, command line, web, LDAP, ... the list goes on. This flexibility will come in handy if (when!) a customer asks for a custom UI.

RTA offers several other features you might want in a package of this type:

  • Application data model reflected by the API data model
  • Remote access to the application
  • Use of standards and existing software by the application
  • Few new protocols and APIs to learn
  • Discovery mechanisms for the application
  • Few constraints on the application
  • Resource locking
  • CPU and memory efficiency


Some Effort...

Some effort is required. In order to make your arrays and structures visible, you need to tell RTA about them. Table information includes things like the name, start address, number of rows and the length of each row. (Remember, a "table" is an array of structures, a "row" is one structure in that array, and a "column" refers to one element in the structure.) Column information includes things like the associated table, the column name, the column's data type, and whether we want any special functions called when the column is read or written. Please see the Quick Start or the API reference for more details.

Valid HTML 4.01!

Node-path: doc/livedemo.html Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 2932 Text-content-md5: 183e55bcdd2784b721abd888dc01087e Content-length: 2942 PROPS-END RTA: Embedded DB API

Run Time Access

Live Demonstration


- Table Editor

RTA comes with a table editor which lets you view and edit any of the tables in your RTA program. The table editor is composed of four short PHP programs which use the information in the two system tables to figure out how to display and edit the other tables in the program.

Click on the link below to give it a try.

You might want to try changing one of the "notes" fields in mytable. Be sure to hit 'reload' to see the effect of your changes.



- Virtual File System

RTA provides a /proc-like access to your program's tables as well as PostgreSQL-like access. In our demo application we have mounted the RTA virtual file system under the root of Apache. This lets you browse the application's tables as directories and files visible with your web browser. Click on the link below to give it a try.


- Direct psql Access

If you have the 7.2 or 7.3 PostgreSQL command line tool psql on your system you can access our demo application directly with

     psql -h www.runtimeaccess.com -p 8888

Once connected, try a few of the following simple SQL commands.

     SELECT * FROM mytable;
     UPDATE mytable SET myint=4 LIMIT 5 OFFSET 10;
     SELECT * FROM mytable;
     UPDATE mytable SET notes="Hi Mom!" LIMIT 1 OFFSET 0;
     SELECT name FROM rta_tables;
     \q


Valid HTML 4.01!

Node-path: doc/myappdb.c Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 6450 Text-content-md5: 616f7eb9bc49ecbfd6d73cc7a9328956 Content-length: 6460 PROPS-END /* A trivial application to demonstrate the RTA-DB package */ /* Build with something like 'gcc -o myappdb myappdb.c -lrtadb' */ #include #include #include /* for 'offsetof' */ #include /* for 'strlen' */ #include /* for 'read/write/close' */ #include #include #include "../src/rta.h" /* Forward references */ int reverse_str(char *tbl, char *col, char *sql, void *pr, int rowid, void *poldrow); #define INSZ 500 #define OUTSZ 50000 #define NOTE_LEN 30 struct MyData { int myint; float myfloat; char notes[NOTE_LEN]; char seton[NOTE_LEN]; }; #define ROW_COUNT 20 struct MyData mydata[ROW_COUNT]; COLDEF mycolumns[] = { { "mytable", /* the table name */ "myint", /* the column name */ RTA_INT, /* it is an integer */ sizeof(int), /* number of bytes */ offsetof(struct MyData, myint), /* location in struct */ 0, /* no flags */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "A sample integer in a table" }, { "mytable", /* the table name */ "myfloat", /* the column name */ RTA_FLOAT, /* it is a float */ sizeof(float), /* number of bytes */ offsetof(struct MyData, myfloat), /* location in struct */ 0, /* no flags */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "A sample float in a table" }, { "mytable", /* the table name */ "notes", /* the column name */ RTA_STR, /* it is a string */ NOTE_LEN, /* number of bytes */ offsetof(struct MyData, notes), /* location in struct */ 0, /* no flags */ (int (*)()) 0, /* called before read */ reverse_str, /* called after write */ "A sample note string in a table" }, { "mytable", /* the table name */ "seton", /* the column name */ RTA_STR, /* it is a string */ NOTE_LEN, /* number of bytes */ offsetof(struct MyData, seton), /* location in struct */ RTA_READONLY, /* a read-only column */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "Another sample note string in a table" }, }; TBLDEF mytbldef = { "mytable", /* table name */ mydata, /* address of table */ sizeof(struct MyData), /* length of each row */ ROW_COUNT, /* number of rows */ (void *) NULL, /* linear array; no need for an iterator */ (void *) NULL, /* no iterator callback data either */ mycolumns, /* array of column defs */ sizeof(mycolumns) / sizeof(COLDEF), /* the number of columns */ "", /* no save file */ "A sample table" }; int main() { int i; /* a loop counter */ int srvfd; /* FD for our server socket */ int connfd; /* FD for conn to client */ struct sockaddr_in srvskt; /* server listen socket */ struct sockaddr_in cliskt; /* socket to the UI/DB client */ int adrlen; char inbuf[INSZ]; /* Buffer for incoming SQL commands */ char outbuf[OUTSZ]; /* response back to the client */ int incnt; /* SQL command input count */ int outcnt; /* SQL command output count */ int dbret; /* return value from SQL command */ /* init mydata */ for (i=0; i= 0) { incnt = read(connfd, &inbuf[incnt], INSZ-incnt); if (incnt <= 0) { close(connfd); connfd = -1; } outcnt = OUTSZ; dbret = dbcommand(inbuf, &incnt, outbuf, &outcnt); switch (dbret) { case RTA_SUCCESS: write(connfd, outbuf, (OUTSZ - outcnt)); incnt = 0; break; case RTA_NOCMD: break; case RTA_CLOSE: close(connfd); connfd = -1; break; } } } } /* reverse_str(), a write callback to replace '<' and '>' with * '.', and to store the reversed string of notes into seton. */ int reverse_str(char *tbl, char *col, char *sql, void *pr, int rowid, void *poldrow) { int i,j; /* loop counters */ i = strlen(mydata[rowid].notes) -1; /* -1 to ignore NULL */ for(j=0 ; i>=0; i--,j++) { if (mydata[rowid].notes[i] == '<' || mydata[rowid].notes[i] == '>') mydata[rowid].notes[i] = '.'; mydata[rowid].seton[j] = mydata[rowid].notes[i]; } mydata[rowid].seton[j] = (char) 0; return(0); } Node-path: doc/myappfs.c Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 4565 Text-content-md5: fe29411f4cccc73ba92067ebd5df04dd Content-length: 4575 PROPS-END /* A trivial application to demonstrate the RTA-FS package */ /* Build with 'gcc myappfs.c -lrtafs -lrtadb' */ #include #include #include /* for 'offsetof' */ #include /* for 'strlen' */ #include /* for 'read/write/close' */ #include "../src/rta.h" /* Forward references */ int reverse_str(char *tbl, char *col, char *sql, void *pr, int rowid, void *poldrow); #define NOTE_LEN 30 struct MyData { int myint; float myfloat; char notes[NOTE_LEN]; char seton[NOTE_LEN]; }; #define ROW_COUNT 20 struct MyData mydata[ROW_COUNT]; COLDEF mycolumns[] = { { "mytable", /* the table name */ "myint", /* the column name */ RTA_INT, /* it is an integer */ sizeof(int), /* number of bytes */ offsetof(struct MyData, myint), /* location in struct */ 0, /* no flags */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "A sample integer in a table" }, { "mytable", /* the table name */ "myfloat", /* the column name */ RTA_FLOAT, /* it is a float */ sizeof(float), /* number of bytes */ offsetof(struct MyData, myfloat), /* location in struct */ 0, /* no flags */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "A sample float in a table" }, { "mytable", /* the table name */ "notes", /* the column name */ RTA_STR, /* it is a string */ NOTE_LEN, /* number of bytes */ offsetof(struct MyData, notes), /* location in struct */ 0, /* no flags */ (int (*)()) 0, /* called before read */ reverse_str, /* called after write */ "A sample note string in a table" }, { "mytable", /* the table name */ "seton", /* the column name */ RTA_STR, /* it is a string */ NOTE_LEN, /* number of bytes */ offsetof(struct MyData, seton), /* location in struct */ RTA_READONLY, /* a read-only column */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "Another sample note string in a table" }, }; TBLDEF mytbldef = { "mytable", /* table name */ mydata, /* address of table */ sizeof(struct MyData), /* length of each row */ ROW_COUNT, /* number of rows */ (void *) NULL, /* linear array; no need for an iterator */ (void *) NULL, /* no iterator callback data either */ mycolumns, /* array of column defs */ sizeof(mycolumns) / sizeof(COLDEF), /* the number of columns */ "", /* no save file */ "A sample table" }; int main() { fd_set rfds; /* read bit masks for select statement */ int i; /* a loop counter */ int fsfd; /* FD to the file system */ /* init mydata */ for (i=0; i= 0) && (FD_ISSET(fsfd, &rfds))) { do_rtafs(); } } } /* reverse_str(), a write callback to replace '<' and '>' with * '.', and to store the reversed string of notes into seton. */ int reverse_str(char *tbl, char *col, char *sql, void *pr, int rowid, void *poldrow) { int i,j; /* loop counters */ i = strlen(mydata[rowid].notes) -1; /* -1 to ignore NULL */ for(j=0 ; i>=0; i--,j++) { if (mydata[rowid].notes[i] == '<' || mydata[rowid].notes[i] == '>') mydata[rowid].notes[i] = '.'; mydata[rowid].seton[j] = mydata[rowid].notes[i]; } mydata[rowid].seton[j] = (char) 0; } Node-path: doc/quickstart.html Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 17229 Text-content-md5: e28fae8513d5fd0e6eb196132c3875a6 Content-length: 17239 PROPS-END RTA: Embedded DB API

Run Time Access

Quick Start

This page gives a brief tutorial on the use of the RTA package by building a trivial application which uses RTA. We define the task, define the tables, give the code, describe how to build and link the application, and describe how to the test the application using the database interface.



Problem Statement

We want to build an application in which we expose an internal array of data structures as both a database and as a file system. The array has twenty structures and each structure has a writeable integer, a writeable float, a writeable string, and a read-only string. Both strings are thirty characters in length. A callback on the writeable string does two things: it replaces any '<' and '>' characters with a '.', and it copies the reversed string into the the read-only string.

The structure would be defined as:

#define NOTE_LEN   30
struct MyData {
    int    myint;
    float  myfloat;
    char   notes[NOTE_LEN];
    char   seton[NOTE_LEN];
};

We allocate storage for the data as:

#define ROW_COUNT  20
struct MyData mydata[ROW_COUNT];

Externally, we want this array of structures to be seen as a Postgres table called "mytable" or as a directory called "mytable" mounted under "/tmp/mydir".


Table Definitions

We need to tell the RTA package about our table. To do this we need to build a COLDEF structure for each of the four columns, and to build a TBLDEF structure for "mydata", the array of structures.

We use an array of four COLDEFs to describe our columns. This is pretty simple:

COLDEF mycolumns[] = {
  {
    "mytable",          /* the table name */
    "myint",            /* the column name */
    RTA_INT,            /* it is an integer */
    sizeof(int),        /* number of bytes */
    offsetof(struct MyData, myint), /* location in struct */
    0,                  /* no flags */
    (int (*)()) 0,      /* called before read */
    (int (*)()) 0,      /* called after write */
    "A sample integer in a table"
  },
  {
    "mytable",          /* the table name */
    "myfloat",          /* the column name */
    RTA_FLOAT,          /* it is a float */
    sizeof(float),      /* number of bytes */
    offsetof(struct MyData, myfloat), /* location in struct */
    0,                  /* no flags */
    (int (*)()) 0,      /* called before read */
    (int (*)()) 0,      /* called after write */
    "A sample float in a table"
  },
  {
    "mytable",          /* the table name */
    "notes",            /* the column name */
    RTA_STR,            /* it is a string */
    NOTE_LEN,           /* number of bytes */
    offsetof(struct MyData, notes), /* location in struct */
    0,                  /* no flags */
    (int (*)()) 0,      /* called before read */
    reverse_str,        /* called after write */
    "A sample note string in a table"
  },
  {
    "mytable",          /* the table name */
    "seton",            /* the column name */
    RTA_STR,            /* it is a string */
    NOTE_LEN,           /* number of bytes */
    offsetof(struct MyData, seton), /* location in struct */
    RTA_READONLY,       /* a read-only column */
    (int (*)()) 0,      /* called before read */
    (int (*)()) 0,      /* called after write */
    "Another sample note string in a table"
  },
};


For each of the four structure elements we gave the associated table name, column name, type, size, position in the struct, flags, read and write callbacks, and a short string to describe it.

We define the table in a similar way:

TBLDEF mytbldef {
    "mytable",           /* table name */
    mydata,              /* address of table */
    sizeof(struct MyData), /* length of each row */
    ROW_COUNT,           /* number of rows */
    mycolumns,           /* array of column defs */
    sizeof(mycolumns) / sizeof(COLDEF),
                         /* the number of columns */
    "",                  /* no save file */
    "A sample table"
};

Note the double quotes to specify no save file. This field is a pointer-to-string and the pointer can not be null, although the string can be.


Database Interface

C Code

The source code for our simple application is available here as myappdb.c. The first section of code of note is the list of include files:

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>             /* for 'offsetof' */
#include <string.h>             /* for 'strlen' */
#include <unistd.h>             /* for 'read/write/close' */
#include <sys/socket.h>
#include <netinet/in.h>
#include "../src/rta.h"

You will need to set the path to the rta.h file to reflect where you install it.

We have already presented the code for the structures and arrays of structures. Let's look at the code in the main() program to perform all initialization:

int main()
{
    int   i;                   /* a loop counter */
    int   srvfd;               /* FD for our server socket */
    int   connfd;              /* FD for conn to client */
    struct sockaddr_in srvskt; /* server listen socket */
    struct sockaddr_in cliskt; /* socket to the UI/DB client */
    int   adrlen;
    char  inbuf[INSZ];         /* Buffer for incoming SQL commands */
    char  outbuf[OUTSZ];       /* response back to the client */
    int   incnt;               /* SQL command input count */
    int   outcnt;              /* SQL command output count */
    int   dbret;               /* return value from SQL command */

    /* init mydata */
    for (i=0; i<ROW_COUNT; i++) {
        mydata[i].myint    = 0;
        mydata[i].myfloat  = 0.0;
        mydata[i].notes[0] = (char) 0;
        mydata[i].seton[0] = (char) 0;
    }

    /* init the RTA package and tell it about mydata */
    rta_init();
    if (rta_add_table(&mytbldef) != RTA_SUCCESS) {
        fprintf(stderr, "Table definition error!\n");
        exit(1);
    }
 

This is pretty standard stuff. We allocate our socket structures and other local variables. We initialize the RTA package and add our one table.

The last piece of initialization is to set up the socket to listen for incoming client connections. Remember that each UI program will treat our program as if it were a Postgres database. We have to accept TCP connections from Postgres clients.

By-the-way: the following code is pretty horrendous. It uses blocking IO, ignores error conditions, and makes wildly optimistic assumptions about socket IO. My goal is to make the code understandable by getting it into as few lines as possible.

    /* We now need to open a socket to listen for incoming
     * client connections. */
    adrlen = sizeof (struct sockaddr_in);
    (void) memset ((void *) &srvskt, 0, (size_t) adrlen);
    srvskt.sin_family = AF_INET;
    srvskt.sin_addr.s_addr = INADDR_ANY;
    srvskt.sin_port = htons (8888);
    srvfd = socket(AF_INET, SOCK_STREAM, 0); /* no error checks! */
    bind(srvfd, (struct sockaddr *) &srvskt, adrlen);
    listen (srvfd, 4);

The main loop in our program waits for a TCP connection from a client, then loops reading the Postgres encoded stream of SQL commands, processing them with dbcommand(), and then writing any results back to the client. While a connection can close for errors, we normally expect the client to request an orderly close to the connection.:

    /* Loop forever accepting client connections */
    while (1) {
        connfd = accept(srvfd, (struct sockaddr *) &cliskt, &adrlen);
        if (connfd < 0) {
            fprintf(stderr, "Error on socket/bind/listen/accept\n");
            exit(1);
        }
        incnt = 0;
        while (connfd >= 0) {
            incnt = read(connfd, &inbuf[incnt], INSZ-incnt);
            if (incnt <= 0) {
                close(connfd);
                connfd = -1;
            }
            outcnt = OUTSZ;
            dbret = dbcommand(inbuf, &incnt, outbuf, &outcnt);
            switch (dbret) {
                case RTA_SUCCESS:
                    write(connfd, outbuf, (OUTSZ - outcnt));
                    incnt = 0;
                    break;
                case RTA_NOCMD:
                    break;
                case RTA_CLOSE:
                    close(connfd);
                    connfd = -1;
                    break;
                case RTA_NOBUF:
                    close(connfd);
                    connfd = -1;
                    break;
            }
        }
    }

The callback function is fairly straightforward. Remember that the write callback is called on a column after the write of all update data to the columns. Our write callback on the 'notes' field is called after the data has been written. It loops through the string replacing greater-than and less-than symbols with a period, and copying a reverse of the string into the 'seton' field.

int reverse_str(char *tbl, char *col, char *sql, void *pr,
                int rowid, void *poldrow)
{
    int   i,j;                 /* loop counters */

    i = strlen(mydata[rowid].notes) -1;  /* -1 to ignore NULL */
    for(j=0 ; i>=0; i--,j++) {
        if (mydata[rowid].notes[i] == '<' ||
            mydata[rowid].notes[i] == '>')
            mydata[rowid].notes[i] = '.';
        mydata[rowid].seton[j] = mydata[rowid].notes[i];
    }
    mydata[rowid].seton[j] = (char) 0;

    return(0);   /* no errors */
}

Build and Link

The information in this section should be sufficient to download, build, and run the sample application.

Download the RTA package from the download page given above. Untar it. Go to the src directory and do a make.

    # tar -xzf rta-0.7.4.tgz
    # cd rta-0.7.4
    # cd src
    # make librtadb.so.2

Download the myappdb.c file (available here). Place the file in the test directory. Build it with the command:

    # gcc myappdb.c -o myappdb -L../src -lrtadb

Note that we are telling the application that the library it needs is in the sibling src directory.

To run the application we will need to tell it where to find the shared object library. From the test directory enter:

    # export LD_LIBRARY_PATH=/home/rta-0.7.4/src
    # ./myappdb &

That is all there is to it. Please use the contact page to report any problems you find.


Test

If all has gone well, we now have an application running which pretends to be a Postgres database server. Only instead of a database, it is our sample application offering up its internal tables for use by various Postgres clients.

psql

The first client to try is the Postgres browser tool, psql. This tool is part of the base Postgres install. Remember that we have our application table, mytable, as well as the RTA internal tables, rta_tables, rta_columns, rta_dbg, and rta_stat. See the API section for a description of these tables.

To start psql to our application enter:

    # psql -h localhost -p 8888

Postgres should respond with:

    Welcome to psql, the PostgreSQL interactive terminal.

    Type:  \copyright for distribution terms
           \h for help with SQL commands
           \? for help on internal slash commands
           \g or terminate with semicolon to execute query
           \q to quit

Let's try some SQL commands to play with the tables.

Give me a list of the tables:

    SELECT name FROM rta_tables;
        name     
    -------------
     rta_tables
     rta_columns
     rta_dbg
     rta_stat
     mytable
    (5 rows)

You do not need to use upper case for the SQL keywords. The psql program accepts both upper and lower case.

Display the contents of mytable:

    SELECT * FROM mytable;
     myint |       myfloat        | notes | seton 
    -------+----------------------+-------+-------
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
    (20 rows)

Set all of the myint values to 44 and display the new table:

    UPDATE mytable SET myint=44;
    UPDATE 20
    SELECT * FROM mytable;

Note that psql tells us how many rows were modified.

Set the notes field to "hi mom!"

    UPDATE mytable SET notes="hi mom!";
    UPDATE 20

Set the notes field of only row 0 to "<b>hi mom!</b>":

    UPDATE mytable SET notes="<b>hi mom!</b>" LIMIT 1 OFFSET 0;
    UPDATE 1

The LIMIT says how many rows to change, and the OFFSET tells where to start the changes. The default LIMIT is all rows, and the default OFFSET is zero.

Turn on the trace of all SQL commands and send the output to both stderr and syslog:

    UPDATE rta_dbg SET trace=1, target=3;

You should now see all the commands echoed on the standard error output of the program. The commands are also being sent to your syslog facility.

C

The C program presented here, rta_client.c, illustrates how to get data out of our application from a C program. You need the postgresql-devel package to build this program. Build and run the program with:

    # gcc rta_client.c -o rta_client -lpq
    # ./rta_client

The program sets the field myint to a value of 44 and then gets and prints the fields myint, myfloat, and notes for all twenty rows in the table.

Note that we test for success in different ways depending on whether or not we expect data back from the command. Note also that the returned data is always a string. You need to scan it into a variable for other processing.

PHP

The PHP program presented here, rta_client.php, illustrates how to get data out of our application from a PHP program. You need the php-pgsql package to run this program. If you are running Apache, check for the PHP interpreter in modules directory. On RH8.0 look for libphp4.so in /etc/httpd/modules. You can verify that the PHP-Postgres library is loaded by checking for pgsql.so in the /usr/lib/php4 directory. Clearly your configuration for PHP and Postgres may be different than what is described here.

This program gets and displays the fields myint, myfloat, and notes for all twenty rows in the table.

Perhaps a better way to evaluate PHP is to install the table editor which comes with the RTA package. Just put the four .php files onto your web server (being sure that PHP and php-pgsql are installed). The table editor is the application running on the Live Demo link above.


Node-path: doc/rta_apps.html Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 954 Text-content-md5: 33ab312b50d2a7e8d2cf66cd6e5a066e Content-length: 964 PROPS-END RTA Table Editor

RTA Table Editor

App NamePort Number
myapp8888
Node-path: doc/rta_client.c Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 1828 Text-content-md5: e6a011864151eca3cd8d9bda09d3169d Content-length: 1838 PROPS-END /* * libpq sample program * gcc rta_client.c -o rta_client -lpq */ #include #include #include "pgsql/libpq-fe.h" /* libpq header file */ char cmd1[] ="UPDATE mytable SET myint=43"; char cmd2[] ="SELECT myint, myfloat, notes FROM mytable"; int main() { char query_string[256]; /* holds constructed SQL query */ PGconn *conn; /* holds database connection */ PGresult *res; /* holds query result */ int i; /* generic loop counter */ /* Connect to the application */ conn = PQconnectdb("host=localhost port=8888"); if (PQstatus(conn) == CONNECTION_BAD) { fprintf(stderr, "Connection to application failed.\n"); fprintf(stderr, "%s", PQerrorMessage(conn)); exit(1); } /* send the first command */ res = PQexec(conn, cmd1); /* send the query */ if (PQresultStatus(res) != PGRES_COMMAND_OK) { fprintf(stderr, "UPDATE command failed.\n"); PQclear(res); PQfinish(conn); exit(1); } PQclear(res); /* free result */ /* send the second command */ res = PQexec(conn, cmd2); /* send the query */ if (PQresultStatus(res) != PGRES_TUPLES_OK) { fprintf(stderr, "SELECT query failed.\n"); PQclear(res); PQfinish(conn); exit(1); } /* display the results of the second command */ printf("\n"); for (i = 0; i < PQntuples(res); i++) { /* loop through all rows returned */ printf("%s\t", PQgetvalue(res, i, 0)); printf("%s\t", PQgetvalue(res, i, 1)); printf("%s", PQgetvalue(res, i, 2)); printf("\n"); } PQclear(res); /* free result */ PQfinish(conn); /* disconnect from the database */ exit(0); } Node-path: doc/rta_client.txt Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 1424 Text-content-md5: 45368fbc7416ca985dc96c170ec7bbde Content-length: 1434 PROPS-END ", "Please verify that the application is running and ", "listening on port 8888.
"); exit(); } // Headings print("\n"); print("\n"); print(" \n"); print(" \n"); print("\n"); // execute query $command = "SELECT myint, myfloat, notes FROM mytable"; $result = pg_exec($conn, $command); if ($result == "") { print("

SQL Command failed!

"); print("

Command: $command

\n"); exit(); } for($row = 0; $row < pg_NumRows($result); $row++) { $myintstr = pg_result($result, $row, 0); $myfloatstr = pg_result($result, $row, 1); $mynotes = pg_result($result, $row, 2); print("
\n"); print("\n"); print("\n"); print("\n"); print("\n"); } print("
MyIntMyFloatNotes
$myintstr$myfloatstr$mynotes
\n"); // free the result and close the connection pg_freeresult($result); pg_close($conn); ?> Node-path: doc/rta_edit.php Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 3829 Text-content-md5: de3557579e16011cfe600d0daf6cc068 Content-length: 3839 PROPS-END Edit Row

Edit $tbl, row $row

\n"); // Suppress Postgres error messages error_reporting(error_reporting() & 0xFFFD); // connect to the database $c1 = pg_connect("localhost", "$port", "bsmith"); if ($c1 == "") { printf("$s%s%s", "Unable to connect to application.
", "Please verify that the application is running and ", "listening on port $port.
"); exit(); } // Give URL for form processing print("
\n"); print("\n"); print("\n"); print("\n"); // print name, help, value of each column print("
\n"); // execute query $command = "SELECT name, flags, help, length, type FROM rta_columns WHERE table='$tbl'"; $r1 = pg_exec($c1, $command); if ($r1 == "") { print("

SQL Command failed!

"); print("

Command: $command

\n"); exit(); } print("
\n"); print("\n"); for($col = 0; $col < pg_NumRows($r1); $col++) { $colname = pg_result($r1, $col, 0); $colflags = pg_result($r1, $col, 1); $colhelp = pg_result($r1, $col, 2); $collength = pg_result($r1, $col, 3); $coltype = pg_result($r1, $col, 4); if (($coltype != 0) && // "0" is the type for strings, See rta.h ($coltype != 4)) // "4" is the type for pointer to string. $collength = 20; // Get column value from table $command = "SELECT \"$colname\" FROM \"$tbl\" LIMIT 1 OFFSET $row"; $r2 = pg_exec($c1, $command); if ($r2 == "") { print("

SQL Command failed!

"); print("

Command: $command

\n"); exit(); } $colvalue = pg_result($r2, 0, 0); pg_freeresult($r2); // Display the column print("
"); if ($colflags & 2) // "2" indicates a read-only field. See rta.h { print("\n"); } else { print("\n"); } } print("
ColumnValue
$colname
$colhelp
$colvalue
"); print("
\n"); print("

\n"); // free the result and close the connection pg_freeresult($r1); pg_close($c1); ?> Node-path: doc/rta_tables.php Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 2292 Text-content-md5: cead02769115731f5cb768af09314b13 Content-length: 2302 PROPS-END RTA Table Editor

RTA Table Editor

", "Please verify that the application is running and ", "listening on port $port.
"); exit(); } // Headings print("\n"); print("\n"); // execute query $command = "SELECT name, help, nrows FROM rta_tables"; $result = pg_exec($conn, $command); if ($result == "") { print("

SQL Command failed!

"); print("

Command: $command

\n"); exit(); } for($row = 0; $row < pg_NumRows($result); $row++) { $tblname = pg_result($result, $row, 0); $tblhelp = pg_result($result, $row, 1); $tblrows = pg_result($result, $row, 2); print("
\n\n"); print("\n"); } print("
Table NameDescription
$tblname$tblhelp
\n"); // free the result and close the connection pg_freeresult($result); pg_close($conn); ?> Node-path: doc/rta_update.php Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 2491 Text-content-md5: a90271187fca7ae16e1a33b5a28cc331 Content-length: 2501 PROPS-END Edit Row

Update $tbl, row $row


\n"); // Suppress Postgres error messages error_reporting(error_reporting() & 0xFFFD); // connect to the database $c1 = pg_connect("localhost", "$port", "anyuser"); if ($c1 == "") { printf("$s%s", "Unable to connect to application.
", "Please verify that the application is running and ", "listening on port $port.
"); exit(); } // Build SQL UPDATE command. $command = "UPDATE $tbl SET "; $count = count($_POST); for ($index=3; $index < $count; $index++) { // use "htmlentities()" to protect from malicious HTML // $value=htmlentities(next($_POST)); // $key = htmlentities(key($_POST)); $value=next($_POST); $key = key($_POST); if ($index > 3) $command = "$command, \"$key\" = \"$value\" "; else $command = "$command \"$key\" = \"$value\" "; } $command = "$command LIMIT 1 OFFSET $row"; // execute query $r1 = pg_exec($c1, $command); if ($r1 == "") { print("

Update failed!

"); print("

Please verify input values.

\n"); print("

Command: $command

\n"); exit(); } // Update succeeded. Say so. print("

Update succeeded."); print("

\n

Command: $command

\n"); // free the result and close the connection pg_freeresult($r1); pg_close($c1); ?> Node-path: doc/rta_view.php Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 3581 Text-content-md5: c73242d845afc37310aaabdab8cd9a7c Content-length: 3591 PROPS-END Table View
$tbl
\n"); // Suppress Postgres error messages error_reporting(error_reporting() & 0xFFFD); // connect to the database $connection = pg_connect("localhost", "$port", "bsmith"); if ($connection == "") { printf("$s%s%s", "Unable to connect to application.
", "Please verify that the application is running and ", "listening on port $port.
"); exit(); } // print each row in a table print("
\n"); // Get and print column names $command = "SELECT name, flags FROM rta_columns WHERE table = $tbl"; $result = pg_exec($connection, $command); if ($result == "") { print("

SQL Command failed!

"); print("

Command: $command

\n"); exit(); } // A flag to say there's at least one editable column. $readonly = 2; // "2" indicates a read-only column. See rta.h print("
\n"); for($row = 0; $row < pg_NumRows($result); $row++) { $colname = pg_result($result, $row, 0); $colflags = pg_result($result, $row, 1); print(""); $readonly = $readonly & $colflags; } if ($readonly == 0) print("\n"); print("\n"); pg_freeresult($result); // execute query $command = "SELECT * FROM $tbl LIMIT 20 OFFSET $offset"; $result = pg_exec($connection, $command); if ($result == "") { print("

SQL Command failed!

"); print("

Command: $command

\n"); exit(); } for($row = 0; $row < pg_NumRows($result); $row++) { print("
\n"); for($field = 0; $field < pg_numfields($result); $field++) { print("\n"); } // Add link to edit the row if editable. if ($readonly == 0) { $rowindex = $offset + $row; print(""); } print("\n"); } print("
$colname 
"); print(pg_result($result, $row, $field)); print("(edit)
\n"); // Add link to next set of rows if needed if ($offset + 20 < $nrows) { $offset = $offset + 20; $nextcount = $nrows - $offset; if ($nrows - $offset > 20) $nextcount = 20; print("

Next $nextcount rows >

\n"); } // free the result and close the connection pg_freeresult($result); pg_close($connection); ?> Node-path: src Node-kind: dir Node-action: add Prop-content-length: 10 Content-length: 10 PROPS-END Node-path: src/Makefile Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 1953 Text-content-md5: 857b266f5e7fd4b7fe9a5b238d0ababf Content-length: 1963 PROPS-END ################################################################ # Run Time Access # Copyright (C) 2003-2006 Robert W Smith (bsmith@linuxtoys.org) # # This program is distributed under the terms of the GNU LGPL. # See the file COPYING file. ################################################################ ################################################################ # Makefile -- to make the rta library. # Add or remove debug stuff to meet your needs. # The "standard" target runs the source through indent to # give the source a standardized look. ################################################################ # We use the default make rules for .c and .l CC = gcc OPT = -fPIC -c -I. DEBUG = -g -ggdb -DDEBUG -DYYDEBUG -ggdb -Wall CFLAGS = $(OPT) $(DEBUG) OBJS = api.o token.o parse.tab.o do_sql.o rtatables.o LIBS = default: librtadb.so.2 librtadb.so.2: $(OBJS) gcc -ggdb -Wall -shared -Wl,-soname,librtadb.so.2 \ -o librtadb.so.2.0 $(OBJS) $(LIBS) -lc ln -sf librtadb.so.2.0 librtadb.so.2 ln -sf librtadb.so.2.0 librtadb.so ar -rcs librtadb.a $(OBJS) token.o: parse.tab.c do_sql.h parse.tab.c: parse.y do_sql.h yacc -dv -bparse parse.y api.o: api.c rta.h do_sql.h do_sql.o: do_sql.c do_sql.h rta.h rtatables.o: rtatables.c do_sql.h rta.h standard: clean for i in *.h *.c ; \ do \ indent -bad -bap -nbbb -br -cdw -cli2 -cbi0 -saf -nut \ -sai -saw -di9 -nbc -bfda -bls -lp -ci2 -i2 -nbbo -l72 \ -fca -nbfda -nbfde -nlp -bbo -sob -cd24 -ts0 -npcs -nut \ -T TBLDEF -T COLDEF $$i ; \ rm $$i~ ; \ done install: /usr/bin/install -m 644 librta* /usr/local/lib; /usr/bin/install -m 644 rta.h /usr/local/include ln -sf /usr/local/lib/librtadb.so.2.0 /usr/local/lib/librtadb.so.2 ln -sf /usr/local/lib/librtadb.so.2.0 /usr/local/lib/librtadb.so uninstall: rm /usr/local/lib/librta*; rm /usr/local/include/rta.h clean: rm -rf *.o parse.tab.? token.c lib* parse.output parse.save *~ Node-path: src/api.c Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 22829 Text-content-md5: 9707209a6f0a18e35bf80d248b496566 Content-length: 22839 PROPS-END /*************************************************************** * Run Time Access * Copyright (C) 2003-2006 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 #include /* for mkstemp() */ #include /* for dirname() */ #include /* for strlen() */ #include /* for PATH_MAX */ #include #include /* for stat() */ #include /* for stat() */ #include /* for stat() */ #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. * Ntbl starts out with a -1 flag to indicate that we are not * initialized. */ TBLDEF *Tbl[MX_TBL]; int Ntbl = -1; COLDEF *Col[MX_COL]; int Ncol; extern struct RtaDbg rtadbg; static char *ConfigDir = (char *) 0; int is_reserved(char *pword); /*************************************************************** * rta_init(): - Initialize all internal system tables. * * Input: None. * Output: None. **************************************************************/ void rta_init() { int i; /* loop index */ extern TBLDEF rta_tablesTable; extern TBLDEF rta_columnsTable; extern TBLDEF rta_dbgTable; extern TBLDEF rta_statTable; extern void restart_syslog(); for (i = 0; i < MX_TBL; i++) { Tbl[i] = (TBLDEF *) 0; } Ntbl = 0; /* add system and internal tables here */ (void) rta_add_table(&rta_tablesTable); (void) rta_add_table(&rta_columnsTable); (void) rta_add_table(&rta_dbgTable); (void) rta_add_table(&rta_statTable); restart_syslog((char *) 0, (char *) 0, (char *) 0, (void *) 0, (void *) 0, 0); } /*************************************************************** * rta_config_dir(): - Set the default directory for all RTA * savefiles. The directory specified here is prepended to * all file names before a load of the .sql file is attempted. * * Input: char * -- the config directory or path * Output: 0 if the dir seems OK, -1 otherwise **************************************************************/ int rta_config_dir(char *configdir) { struct stat statbuf; /* to verify input is a directory */ int len; /* length of the path */ /* Initialize the RTA tables if this is the first call to add_table */ if (Ntbl == -1) rta_init(); /* Perform some sanity checks */ if (!stat(configdir, &statbuf) && S_ISDIR(statbuf.st_mode)) { ConfigDir = strdup(configdir); if (ConfigDir) { len = strlen(ConfigDir); if (len != 1 && ConfigDir[len -1] == '/') { ConfigDir[len - 1] = (char) 0; } return(0); } } return(-1); } /*************************************************************** * 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 RtaStat rtastat; extern TBLDEF rta_columnsTable; int i, j; /* a loop index */ /* Initialize the RTA tables if this is the first call to add_table */ if (Ntbl == -1) rta_init(); /* 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 length of table name */ if (strlen(ptbl->name) > MXTBLNAME) { rtastat.nrtaerr++; if (rtadbg.rtaerr) rtalog(LOC, Er_Tname_Big, ptbl->name); return (RTA_ERROR); } /* verify table name is not a reserved word */ if (is_reserved(ptbl->name)) { rtastat.nrtaerr++; if (rtadbg.rtaerr) rtalog(LOC, Er_Reserved, 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->name, ptbl->cols[i].name); return (RTA_ERROR); } } } /* verify column name length, help length, data type, flag contents, and that column table name is valid */ 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 (is_reserved(ptbl->cols[i].name)) { rtastat.nrtaerr++; if (rtadbg.rtaerr) rtalog(LOC, Er_Reserved, 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); } if (strcmp(ptbl->cols[i].table, ptbl->name)) { rtastat.nrtaerr++; if (rtadbg.rtaerr) rtalog(LOC, Er_Col_Name, 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 protocol uses the first byte to identify the packet type. I * If the first byte is a zero the packet is either a start-up * packet or a cancel packet. If the first byte is not a zero * the packet is a command packet. Most packets have the command * byte followed by four bytes of packet length. Note that * multi-byte data is sent with the most significant byte first. * Please see the full documentation in the "PostgreSQL 7.4 * Developer's Guide" at http://www.postgresql.org/ * **************************************************************/ /*************************************************************** * 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 * RTA_NOBUF - insufficient output buffer space **************************************************************/ int dbcommand(char *buf, int *nin, char *out, int *nout) { extern struct RtaStat rtastat; int length; /* length of the packet if old protocol */ /* startup or cancel 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) ((unsigned int) (0xff & buf[3]) + ((unsigned int) (0xff & buf[2]) << 8) + ((unsigned int) (0xff & buf[1]) << 16)); /* Is the whole packet here? If not, consume no input, write no output */ if (*nin < length) { return (RTA_NOCMD); } /* The first packet can be a request for an SSL connection. Look for this and return an 'N' to indicate that we do not support SSL. The packet is '00 00 00 08 04 d2 16 2f'. Note that '04 d2' and '16 2f' are 1234 and 5678 respectively. */ if (length == 8 && buf[4] == (char) 0x04 && buf[5] == (char) 0xd2 && buf[6] == (char) 0x16 && buf[7] == (char) 0x2f) { *out = 'N'; out++; *nout -= 1; *nin -= length; return (RTA_SUCCESS); } /* Look for a start-up request packet. Do a sanity check since the minimum startup packet is 13 bytes (4 length, 4 protocol, 'user', and a null). */ if (length < 13) { /* unknown, ignore, (log?) */ *nin -= length; return (RTA_SUCCESS); } /* A start-up packet if 0300 in the protocol field */ if (buf[4] == 0 && buf[5] == 3 && buf[6] == 0 && buf[7] == 0) { /* Currently we do not authenticate the user. If you want to add real authentication, this is the place to add it. */ /* *INDENT-OFF* */ /* This is the canned response we send on all start-ups. See the Postgres protocol specification for details. 'R', int32(length), int32(0) 'S', int32(length), "client_encoding", null, "SQL_ASCII", null 'S', int32(length), "DataStyle", null, "ISO, MDY", null 'S', int32(length), "is_superuser", null, "on", null 'S', int32(length), "server_version", "7.4", null 'S', int32(length), "session_authorization", null, "postgres", null 'K', int32(length), int32(pid of backend), int32(secret key) 'Z', int32(length), 'I' */ unsigned char reply[164] = { 'R',0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x00, 'S',0x00,0x00,0x00,0x1e,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x65, 0x6e,0x63,0x6f,0x64,0x69,0x6e,0x67,0x00,0x53,0x51,0x4c,0x5f, 0x41,0x53,0x43,0x49,0x49,0x00, 'S',0x00,0x00,0x00,0x17,0x44,0x61,0x74,0x65,0x53,0x74,0x79,0x6c, 0x65,0x00,0x49,0x53,0x4f,0x2c,0x20,0x4d,0x44,0x59, 0x00, 'S',0x00,0x00,0x00,0x14,0x69,0x73,0x5f,0x73,0x75,0x70,0x65,0x72, 0x75,0x73,0x65,0x72,0x00,0x6f,0x6e,0x00, 'S',0x00,0x00,0x00,0x17,0x73,0x65,0x72,0x76,0x65,0x72,0x5f,0x76, 0x65,0x72,0x73,0x69,0x6f,0x6e,0x00,0x37,0x2e,0x34,0x00, 'S',0x00,0x00,0x00,0x23,0x73,0x65,0x73,0x73,0x69,0x6f,0x6e,0x5f, 0x61,0x75,0x74,0x68,0x6f,0x72,0x69,0x7a,0x61,0x74,0x69,0x6f, 0x6e,0x00,0x70,0x6f,0x73,0x74,0x67,0x72,0x65,0x73,0x00, 'K',0x00,0x00,0x00,0x0c,0x00,0x00,0x36,0x94,0x56,0xf4,0x8d,0x68, 'Z',0x00,0x00,0x00,0x05,0x49}; /* *INDENT-ON* */ /* Verify that the buffer has enough room for the response */ if (*nout < 164) { rtastat.nsqlerr++; if (rtadbg.sqlerr) rtalog(LOC, Er_No_Space); return (RTA_NOBUF); } *nin -= length; (void) memcpy(out, reply, 164); *nout -= 164; rtastat.nauth++; return (RTA_SUCCESS); } else if (length == 16) { /* a cancel request */ /* ignore the request for now */ *nin -= length; return (RTA_SUCCESS); } else { /* unknown, ignore, (log?) */ *nin -= length; return (RTA_SUCCESS); } } else if (buf[0] == 'Q') { /* a query request */ /* the Postgres 0300 protocol has a 32 bit length after the 1 byte command. Verify that we have enough bytes to get the length */ if (*nin < 5) { return (RTA_NOCMD); } /* Get length and verify that we have all the bytes. Note the quiet assumption that the length is 24 bits. */ length = (int) ((unsigned int) (0xff & buf[4]) + ((unsigned int) (0xff & buf[3]) << 8) + ((unsigned int) (0xff & buf[2]) << 16)); /* add one to account for the 'Q' */ length++; if (*nin < length) { return (RTA_NOCMD); } /* Got a complete command; do it. (buf[5] since the SQL follows the 'Q' and length) */ SQL_string(&buf[5], (*nin - 5), out, nout); *nin -= length; /* to swallow the cmd */ return (RTA_SUCCESS); } else if (buf[0] == 'X') { /* a terminate request */ return (RTA_CLOSE); } /* an unknown request (should be logged?) */ return (RTA_CLOSE); } /*************************************************************** * 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 RtaStat rtastat; int sr; /* the Size of each Row in the table */ int rx; /* Row indeX */ void *pr; /* Pointer to the row in the table/column */ 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]; /* full path/file name */ 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 */ /* Fill in the path with the full path to the config file */ path[0] = (char) 0; if (fname[0] == '/') { (void) strncpy(path, fname, PATH_MAX); path[PATH_MAX-1] = (char) 0; } else { if (ConfigDir) { (void) strcpy(path, ConfigDir); strcat(path, "/"); } strcat(path, fname); } /* Do a sanity check on the lengths of the paths involved */ if (strlen(path) + strlen("/tmpXXXXXX") > PATH_MAX -1) { rtastat.nsyserr++; if (rtadbg.syserr) rtalog(LOC, Er_No_Save, ptbl->name, path); return (RTA_ERROR); } /* Open a temp file in the same directory as the users target file */ (void) strcpy(tfile, path); (void) dirname(tfile); (void) strcat(tfile, "/tmpXXXXXX"); fd = mkstemp(tfile); if (fd < 0) { rtastat.nsyserr++; if (rtadbg.syserr) rtalog(LOC, Er_No_Save, ptbl->name, tfile); return (RTA_ERROR); } ftmp = fdopen(fd, "w"); if (ftmp == (FILE *) 0) { rtastat.nsyserr++; if (rtadbg.syserr) rtalog(LOC, Er_No_Save, ptbl->name, tfile); return (RTA_ERROR); } /* OK, temp file is open and ready to receive table data */ /* Get row length and a pointer to the first row */ sr = ptbl->rowlen; rx = 0; if (ptbl->iterator) pr = (ptbl->iterator) ((void *) NULL, ptbl->it_info, rx); else pr = ptbl->address; /* for each row ..... */ while (pr) { did_header = 0; did_1_col = 0; for (cx = 0; cx < ptbl->ncol; cx++) { if ((!(ptbl->cols[cx].flags & RTA_DISKSAVE)) || (ptbl->cols[cx].flags & RTA_READONLY)) 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 = (char *)pr + 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", *((llong *) pd)); break; case RTA_PLONG: fprintf(ftmp, "= %lld", **((llong **) 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); rx++; if (ptbl->iterator) pr = (ptbl->iterator) (pr, ptbl->it_info, rx); else { if (rx >= ptbl->nrows) pr = (void *) NULL; else pr = (char *)ptbl->address + (rx * sr); } } /* 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, path) != 0) { rtastat.nsyserr++; if (rtadbg.syserr) rtalog(LOC, Er_No_Save, ptbl->name, path); 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 RtaStat 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 */ char path[PATH_MAX]; /* full path/file name */ /* 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. */ /* Fill in the path with the full path to the config file */ path[0] = (char) 0; if (fname[0] == '/') { (void) strncpy(path, fname, PATH_MAX); path[PATH_MAX] = (char) 0; } else { if (ConfigDir) { (void) strcpy(path, ConfigDir); strcat(path, "/"); } strcat(path, fname); } /* Open the savefile of SQL UPDATE statements */ fp = fopen(path, "r"); if (fp == (FILE *) 0) { rtastat.nsyserr++; if (rtadbg.syserr) rtalog(LOC, Er_No_Load, ptbl->name, path); 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, strlen(line), reply, &nreply); if (!strncmp(line, "UPDATE 1", 8)) { /* SQL command failed! Report error */ rtastat.nsyserr++; if (rtadbg.syserr) rtalog(LOC, Er_No_Load, ptbl->name, fname); return (RTA_ERROR); } } ptbl->savefile = savefilename; return (RTA_SUCCESS); } /*************************************************************** * is_reserved(): - Check to see if a word is one of our SQL * reserved words. * * Input: pword - pointer to the word to check * * Return: 0 if not a reserved word, 1 if it is. **************************************************************/ int is_reserved(char *pword) { return (!strcasecmp(pword, "SELECT") || !strcasecmp(pword, "UPDATE") || !strcasecmp(pword, "FROM") || !strcasecmp(pword, "WHERE") || !strcasecmp(pword, "LIMIT") || !strcasecmp(pword, "OFFSET") || !strcasecmp(pword, "SET")); } Node-path: src/do_sql.c Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 37577 Text-content-md5: 709200dc93cf049d560a8fa58cb4710f Content-length: 37587 PROPS-END /*************************************************************** * Run Time Access * Copyright (C) 2003-2006 Robert W Smith (bsmith@linuxtoys.org) * * This program is distributed under the terms of the GNU LGPL. * See the file COPYING file. **************************************************************/ /*************************************************************** * do_sql.c: The subroutines in this file execute the SQL * commands received from the UIs. **************************************************************/ #include #include #include /* for va_arg */ #include #include #include "do_sql.h" struct Sql_Cmd cmd; extern TBLDEF *Tbl[]; extern int Ntbl; extern COLDEF *Col[]; extern int Ncol; extern struct RtaStat rtastat; extern struct RtaDbg rtadbg; /*************************************************************** * How this stuff works: * The main program accepts TCP connections from Postgres * clients. The main program collects the SQL command from * the clients and calls dbcommand() to handle the protocol * between the DB client and our embedded DB server. The * routine dbcommand() handles the actual SQL by setting up * a lex buffer and calling yyparse() to invoke the yacc/lex * scanner for our micro-SQL. The scanner parses the command * and places the relevant information into the "sql_cmd" * structure. If the parse of the command is successful, the * routine do_sql() is called to actually execute the command. **************************************************************/ /*************************************************************** * do_sql(): - Execute the SQL select or update command in the * sql_cmd structure. * * Input: A buffer to store the output * The number of free bytes in the buffer * Output: The number of free bytes in the buffer * Effects: Lots. This is where the read and write * callbacks are executed. ***************************************************************/ void do_sql(char *buf, int *nbuf) { switch (cmd.command) { case RTA_SELECT: verify_table_name(buf, nbuf); if (cmd.err) return; verify_select_list(buf, nbuf); if (cmd.err) return; verify_where_list(buf, nbuf); if (cmd.err) return; /* The command looks good. Send the Row Desc. */ buf += send_row_description(buf, nbuf); if (cmd.err) return; do_select(buf, nbuf); rtastat.nselect++; break; case RTA_UPDATE: verify_table_name(buf, nbuf); if (cmd.err) return; verify_update_list(buf, nbuf); if (cmd.err) return; verify_where_list(buf, nbuf); if (cmd.err) return; /* The command looks good. Update and do callbacks */ do_update(buf, nbuf); rtastat.nupdate++; break; default: syslog(LOG_ERR, "DB error: no SQL cmd\n"); break; } } /*************************************************************** * verify_table_name(): - Verify that the table referenced in * the command exists. We want to make sure everything is * correct before we start any of the actual command since we * we don't want side effects left over from a failed command. * On error, we output the error message and set the err flag. * * Input: A buffer to store the output * The number of free bytes in the buffer * Output: The number of free bytes in the buffer * Effects: The err flag, and the output buffer ***************************************************************/ void verify_table_name(char *buf, int *nbuf) { int i; /* temp integers */ /* Verify that the table exists */ for (i = 0; i < Ntbl; i++) { if ((Tbl[i] != (TBLDEF *) 0) && (!strncmp(cmd.tbl, Tbl[i]->name, MXTBLNAME))) { break; } } if ((Tbl[i] == (TBLDEF *) 0) || (i == Ntbl)) { send_error(LOC, E_NOTABLE, cmd.tbl); return; } /* Save the pointer to the table in TBLDEFS for later use */ cmd.itbl = i; cmd.ptbl = Tbl[i]; } /*************************************************************** * verify_select_list(): - Verify the list of column to display * in a select statement. We want to make sure everything is * correct before we start any of the actual select since we * we don't want side effects left over from a failed select. * This is where we compute the length of each output line so * we can test buffer space later. * On error, we output the error message and set the err flag. * * Input: A buffer to store the output * The number of free bytes in the buffer * Output: The number of free bytes in the buffer * Effects: The err flag, and the output buffer ***************************************************************/ void verify_select_list(char *buf, int *nbuf) { COLDEF *coldefs; /* the table of columns */ int ncols; /* the number of columns */ int i, j; /* temp integers */ /* Verify that each column exists in the right table */ coldefs = cmd.ptbl->cols; ncols = cmd.ptbl->ncol; /* Handle the special case of a SELECT * FROM .... We look for the '*', then for each column in the table, free any memory in cmd.cols, get the size of the column name, allocate memeory, copy the column name, and put the pointer into cmd.cols. */ if (cmd.cols[0][0] == '*') { /* they are asking for the full column list */ for (i = 0; i < ncols; i++) { if (cmd.cols[i]) free(cmd.cols[i]); cmd.cols[i] = malloc(strlen(coldefs[i].name) + 1); if (cmd.cols[i] == (void *) 0) { rtastat.nsyserr++; if (rtadbg.syserr) rtalog(LOC, Er_No_Mem); cmd.err = 1; return; } strcpy(cmd.cols[i], coldefs[i].name); /* Save pointer to the column in COLDEFS */ cmd.pcol[i] = &(coldefs[i]); } /* Give the command the correct # cols to display */ cmd.ncols = ncols; } /* OK, scan the columns definitions to verify that the columns in the command are really columns in the table. Scan is ...for each col in select list ... */ for (j = 0; j < NCMDCOLS; j++) { if (cmd.cols[j] == (char *) 0) { return; /* select list is OK */ } /* Scan is ...for each column definition ... */ for (i = 0; i < ncols; i++) { /* Is this column in the table of interest? */ if (!strncmp(cmd.cols[j], coldefs[i].name, MXCOLNAME)) { /* Save pointer to the column in COLDEFS */ cmd.pcol[j] = &(coldefs[i]); /* Compute length of output line */ switch (coldefs[i].type) { case RTA_STR: case RTA_PSTR: cmd.nlineout += coldefs[i].length; break; case RTA_PTR: case RTA_INT: case RTA_PINT: cmd.nlineout += MX_INT_STRING; break; case RTA_LONG: case RTA_PLONG: cmd.nlineout += MX_LONG_STRING; break; case RTA_FLOAT: case RTA_PFLOAT: cmd.nlineout += MX_FLOT_STRING; break; } cmd.nlineout += 4; /* 4 byte length in reply */ break; } } /* We scanned column defs. Error if not found */ if (i == ncols) { send_error(LOC, E_NOCOLUMN, cmd.cols[j]); return; } } /* The select column list is OK */ return; } /*************************************************************** * verify_where_list(): - Verify the relations in a WHERE * clause. We want to make sure everything is correct before * we start any of the actual select/update since we we don't * want side effects left over from a failed command. * On error, we output the error message and set the err flag. * We check that each column is in the specified table, and that * the type of the data in each phrase (eg, x = 12) matches the * data type for that column. * * Input: A buffer to store the output * The number of free bytes in the buffer * Output: The number of free bytes in the buffer * Effects: The err flag and the output buffer on error ***************************************************************/ void verify_where_list(char *buf, int *nbuf) { COLDEF *coldefs; /* the table of columns */ int ncols; /* the number of columns */ int i, j; /* Loop index */ /* Verify that each column exists in the right table */ coldefs = cmd.ptbl->cols; ncols = cmd.ptbl->ncol; /* scan the columns definitions to verify that the columns in the command are really columns in the table. Scan is ...for each col in where list ... */ for (j = 0; j < NCMDCOLS; j++) { if (cmd.whrcols[j] == (char *) 0) { return; } /* Scan is ...for each column definition ... */ for (i = 0; i < ncols; i++) { /* we are on a column def for the right table */ if (!strncmp(cmd.whrcols[j], coldefs[i].name, MXCOLNAME)) { /* column is valid, now check data type. Must be string or num, if num, need val */ if ((coldefs[i].type == RTA_STR) || (coldefs[i].type == RTA_PSTR) || ((coldefs[i].type == RTA_INT) && (sscanf(cmd.whrvals[j], "%d", &(cmd.whrints[j])) == 1)) || ((coldefs[i].type == RTA_PINT) && (sscanf(cmd.whrvals[j], "%d", &(cmd.whrints[j])) == 1)) || ((coldefs[i].type == RTA_PTR) && (sscanf(cmd.whrvals[j], "%d", &(cmd.whrints[j])) == 1)) || ((coldefs[i].type == RTA_LONG) && (sscanf(cmd.whrvals[j], "%lld", &(cmd.whrlngs[j])) == 1)) || ((coldefs[i].type == RTA_PLONG) && (sscanf(cmd.whrvals[j], "%lld", &(cmd.whrlngs[j])) == 1)) || ((coldefs[i].type == RTA_FLOAT) && (sscanf(cmd.whrvals[j], "%f", &(cmd.whrflot[j])) == 1)) || ((coldefs[i].type == RTA_PFLOAT) && (sscanf(cmd.whrvals[j], "%f", &(cmd.whrflot[j])) == 1))) { /* Save WHERE column pointer for later use */ cmd.pwhr[j] = &(coldefs[i]); break; } /* bogus where phrase */ send_error(LOC, E_BADPARSE); return; } } /* We scanned column defs. Error if not found */ if (i == ncols) { send_error(LOC, E_NOCOLUMN, cmd.whrcols[j]); return; } } /* The where column list is OK */ return; } /*************************************************************** * verify_update_list(): - Verify the list of column to update * in an update statement. We want to make sure everything is * correct before we start any of the actual select since we * we don't want side effects left over from a failed select. * On error, we output the error message and set the err flag. * We verify that the columns are indeed part of the specified * table and that the data type of the column matches the data * type of the right-hand-side. * * Input: A buffer to store the output * The number of free bytes in the buffer * Output: The number of free bytes in the buffer * Effects: The err flag and the output buffer on error ***************************************************************/ void verify_update_list(char *buf, int *nbuf) { COLDEF *coldefs; /* the table of columns */ int ncols; /* the number of columns */ int i, j; /* Loop index */ /* Verify that each column exists in the right table */ coldefs = cmd.ptbl->cols; ncols = cmd.ptbl->ncol; /* scan the columns definitions to verify that the columns in the command are really columns in the table. Scan is ...for each col in update list ... */ for (j = 0; j < NCMDCOLS; j++) { if (cmd.cols[j] == (char *) 0) { return; } /* Scan is ...for each column definition ... */ for (i = 0; i < ncols; i++) { if (strncmp(cmd.cols[j], coldefs[i].name, MXCOLNAME)) continue; /* Save pointer to the column in COLDEFS */ cmd.pcol[j] = &(coldefs[i]); /* Verify that column is not read-only */ if (coldefs[i].flags & RTA_READONLY) { send_error(LOC, E_NOWRITE, coldefs[i].name); return; } /* Column exists and is not read-only. Now check data type. If a string, check the string length. We do a '-1' to be sure there is room for a null at the end of the string. */ if ((coldefs[i].type == RTA_STR) || (coldefs[i].type == RTA_PSTR)) { if (strlen(cmd.updvals[j]) <= coldefs[i].length -1) { break; } send_error(LOC, E_BIGSTR, coldefs[i].name); return; } /* Verify conversion of int/long */ if (((coldefs[i].type == RTA_INT) && (sscanf(cmd.updvals[j], "%d", &(cmd.updints[j])) == 1)) || ((coldefs[i].type == RTA_PINT) && (sscanf(cmd.updvals[j], "%d", &(cmd.updints[j])) == 1)) || ((coldefs[i].type == RTA_PTR) && (sscanf(cmd.updvals[j], "%d", &(cmd.updints[j])) == 1)) || ((coldefs[i].type == RTA_LONG) && (sscanf(cmd.updvals[j], "%lld", &(cmd.updlngs[j])) == 1)) || ((coldefs[i].type == RTA_PLONG) && (sscanf(cmd.updvals[j], "%lld", &(cmd.updlngs[j])) == 1)) || ((coldefs[i].type == RTA_FLOAT) && (sscanf(cmd.updvals[j], "%f", &(cmd.updflot[j])) == 1)) || ((coldefs[i].type == RTA_PFLOAT) && (sscanf(cmd.updvals[j], "%f", &(cmd.updflot[j])) == 1))) { break; } /* bogus update list */ send_error(LOC, E_BADPARSE); return; } /* We scanned column defs. Error if not found */ if (i == ncols) { send_error(LOC, E_NOCOLUMN, cmd.cols[j]); return; } } /* The update column list is OK */ return; } /*************************************************************** * do_select(): - Execute a SELECT statement against the DB. * * Input: A buffer to store the output * The number of free bytes in the buffer * Output: The number of free bytes in the buffer * Effects: Maybe lots. This is where the read callbacks * are executed. ***************************************************************/ void do_select(char *buf, int *nbuf) { int sr; /* the Size of each Row in the table */ int rx; /* Row indeX in for() loop */ int wx; /* Where clause indeX in for loop */ void *pr; /* Pointer to the row in the table/column */ void *pd; /* Pointer to the Data in the table/column */ llong cmp; /* has actual relation of col and val */ int dor; /* DO Row == 1 if we should print row */ int npr = 0; /* Number of output rows */ char nprstr[30]; /* string to hold ASCII of npr */ char *startbuf; /* used to compute response length */ char *lenloc; /* points to where 'D' pkt length goes */ int cx; /* Column index while building Data pkt */ int n; /* number of chars printed in sprintf() */ int count; /* number of chars to send as string */ startbuf = buf; /* We loop through all rows in the table in question applying the WHERE condition. If a row matches we perform read callbacks on the selected columns and build a reply with the requested data */ sr = cmd.ptbl->rowlen; rx = 0; if (cmd.ptbl->iterator) pr = (cmd.ptbl->iterator) ((void *) NULL, cmd.ptbl->it_info, rx); else pr = cmd.ptbl->address; /* for each row ..... */ while (pr) { dor = 1; for (wx = 0; wx < cmd.nwhrcols; wx++) { /* execute read callback (if defined) on row */ /* the call back is expected to fill in the data */ /* and return zero on success. */ if (cmd.pwhr[wx]->readcb) { if ((cmd.pwhr[wx]->readcb) (cmd.tbl, cmd.whrcols[wx], cmd.sqlcmd, pr, rx) != 0) { send_error(LOC, E_BADTRIG, cmd.whrcols[wx]); return; } } /* compute pointer to actual data */ pd = (char *)pr + cmd.pwhr[wx]->offset; /* do comparison based on column data type */ switch (cmd.pwhr[wx]->type) { case RTA_STR: cmp = strncmp((char *) pd, cmd.whrvals[wx], cmd.pwhr[wx]->length); break; case RTA_PSTR: cmp = strncmp(*(char **) pd, cmd.whrvals[wx], cmd.pwhr[wx]->length); break; case RTA_INT: cmp = *((int *) pd) - cmd.whrints[wx]; break; case RTA_PINT: cmp = **((int **) pd) - cmd.whrints[wx]; break; case RTA_LONG: cmp = *((llong *) pd) - cmd.whrlngs[wx]; break; case RTA_PLONG: cmp = **((llong **) pd) - cmd.whrlngs[wx]; break; case RTA_PTR: cmp = *((int *) pd) - cmd.whrints[wx]; break; case RTA_FLOAT: cmp = *((float *) pd) - cmd.whrflot[wx]; break; case RTA_PFLOAT: cmp = **((float **) pd) - cmd.whrflot[wx]; break; default: cmp = 1; /* assume no match */ break; } if (!(((cmp == 0) && (cmd.whrrel[wx] == RTA_EQ || cmd.whrrel[wx] == RTA_GE || cmd.whrrel[wx] == RTA_LE)) || ((cmp != 0) && (cmd.whrrel[wx] == RTA_NE)) || ((cmp < 0) && (cmd.whrrel[wx] == RTA_LE || cmd.whrrel[wx] == RTA_LT)) || ((cmp > 0) && (cmd.whrrel[wx] == RTA_GE || cmd.whrrel[wx] == RTA_GT)))) { dor = 0; break; } } if (dor && cmd.offset) cmd.offset--; else if (dor) { /* if we get here, we've passed the WHERE clause and OFFSET filtering. Check the LIMIT filter */ if (npr >= cmd.limit) { break; } /* Verify that the buffer has enough room for this row. Note that we've been adding the maximum field length for each column into nlineout so that it now contains a worst case line length for this table/row. */ if (*nbuf - ((int) (buf - startbuf)) - cmd.nlineout < 100) { /* 100 for CSELECT and margin */ send_error(LOC, E_FULLBUF); return; } /* At this point we have a row which passed the * WHERE clause, is greater than OFFSET and less * than LIMIT. So send it! */ *buf++ = 'D'; /* Data packet */ lenloc = buf; /* Remember location for length */ buf += 4; /* Response length goes here */ ad_int2(&buf, cmd.ncols); /* # of cols in response */ for (cx = 0; cx < cmd.ncols; cx++) { /* execute column read callback (if defined). callback will fill in the data if needed, and return 0 on success */ if (cmd.pcol[cx]->readcb) { if ((cmd.pcol[cx]->readcb) (cmd.tbl, cmd.pcol[cx]->name, cmd.sqlcmd, pr, rx) != 0) { send_error(LOC, E_BADTRIG, cmd.pcol[cx]->name); return; } } /* compute pointer to actual data */ pd = (char *)pr + cmd.pcol[cx]->offset; switch ((cmd.pcol[cx])->type) { case RTA_STR: /* send 4 byte length. Include the length */ count = strlen(pd); /* shorter of field length or strlen */ if (count > cmd.pcol[cx]->length -1) { count = cmd.pcol[cx]->length -1; } ad_int4(&buf, count); ad_str(&buf, pd, count); /* send the response */ break; case RTA_PSTR: count = strlen(*(char **) pd); /* shorter of field length or strlen */ if (count > cmd.pcol[cx]->length -1) { count = cmd.pcol[cx]->length -1; } ad_int4(&buf, count); /* send the response */ ad_str(&buf, *(char **) pd, count); break; case RTA_INT: n = sprintf((buf + 4), "%d", *((int *) pd)); ad_int4(&buf, n); /* send length */ buf += n; break; case RTA_PINT: n = sprintf((buf + 4), "%d", **((int **) pd)); ad_int4(&buf, n); /* send length */ buf += n; break; case RTA_LONG: n = sprintf((buf + 4), "%lld", *((llong *) pd)); ad_int4(&buf, n); buf += n; break; case RTA_PLONG: n = sprintf((buf + 4), "%lld", **((llong **) pd)); ad_int4(&buf, n); buf += n; break; case RTA_PTR: n = sprintf((buf + 4), "%d", *((int *) pd)); ad_int4(&buf, n); /* send length */ buf += n; break; case RTA_FLOAT: n = sprintf((buf + 4), "%20.10f", *((float *) pd)); ad_int4(&buf, n); buf += n; break; case RTA_PFLOAT: n = sprintf((buf + 4), "%20.10f", **((float **) pd)); ad_int4(&buf, n); buf += n; break; } } /* now fill in 'D' response length */ ad_int4(&lenloc, (int) (buf - lenloc)); npr++; } rx++; if (cmd.ptbl->iterator) pr = (cmd.ptbl->iterator) (pr, cmd.ptbl->it_info, rx); else { if (rx >= cmd.ptbl->nrows) pr = (void *) NULL; else pr = (char *)cmd.ptbl->address + (rx * sr); } } /* Add 'C', length(11), 'SELECT', NULL to output */ *buf++ = 'C'; ad_int4(&buf, 11); /* 11= 4+strlen(SELECT)+1 */ ad_str(&buf, "SELECT", 6); *buf++ = 0x00; *nbuf -= (int) (buf - startbuf); /* Log SQL if trace is on */ if (rtadbg.trace) { (void) sprintf(nprstr, "%d", npr); rtalog(LOC, Er_Trace_SQL, cmd.sqlcmd, nprstr); } } /*************************************************************** * send_row_description(): - We have analyzed the select command * and it seems OK. We start the reply by sending the row * description first. * * Input: A buffer to store the output * The number of free bytes in the buffer * Output: Returns the number of bytes written to buffer * Effects: Lots. This is where the write callbacks * are executed. ***************************************************************/ int send_row_description(char *buf, int *nbuf) { char *startbuf; /* used to compute response length */ int i; /* loop index */ int size; /* estimated/actual size of response */ startbuf = buf; /* Verify that the buffer has enough room for this reply. (See the Postgres protocol description for an understanding of the next two lines.) */ size = 7; /* sizeof 'T', length, and int2 */ size += cmd.ncols * (MXCOLNAME + 1 + 4 + 2 + 4 + 2 + 4 + 2); if (*nbuf - size < 100) { /* 100 just for safety */ send_error(LOC, E_FULLBUF); return ((int) (cmd.out - startbuf)); /* ignored */ } /* Send the row description header */ *buf++ = 'T'; /* row description */ buf += 4; /* put pkt length here later */ ad_int2(&buf, cmd.ncols); /* num fields */ for (i = 0; i < cmd.ncols; i++) { ad_str(&buf, cmd.cols[i], strlen(cmd.cols[i])); /* column name */ *buf++ = (char) 0; /* send the NULL */ /* Add table index */ ad_int4(&buf, cmd.itbl); /* Add the column index */ ad_int2(&buf, i); /* OIDs are tbl index times max col + col index */ ad_int4(&buf, (cmd.itbl * NCMDCOLS) + i); /* set size/modifier based on type */ switch ((cmd.pcol[i])->type) { case RTA_STR: case RTA_PSTR: ad_int2(&buf, -1); /* length */ ad_int4(&buf, 29); /* type modifier */ break; case RTA_INT: case RTA_PINT: ad_int2(&buf, sizeof(int)); /* length */ ad_int4(&buf, -1); /* type modifier */ break; case RTA_LONG: case RTA_PLONG: ad_int2(&buf, sizeof(llong)); /* length */ ad_int4(&buf, -1); /* type modifier */ break; case RTA_FLOAT: case RTA_PFLOAT: ad_int2(&buf, sizeof(float)); /* length */ ad_int4(&buf, -1); /* type modifier */ break; case RTA_PTR: ad_int2(&buf, sizeof(void *)); /* length */ ad_int4(&buf, -1); /* type modifier */ break; } /* Add the format type. 0==text format */ ad_int2(&buf, 0); } size = (int) (buf - startbuf); /* actual response size */ *nbuf -= size; /* store packet length -1 (the 'T' is not included *) */ startbuf++; /* skip over the 'T' */ ad_int4(&startbuf, (size - 1)); return (size); } /*************************************************************** * send_error(): - Send an error message back to the requesting * program or process. * * Input: The file name and line number where the error * was detected, and the format and optional * argument of the error message. * Output: void * Effects: Puts data in the command response buffer. ***************************************************************/ void send_error(char *filename, int lineno, char *fmt, char *arg) { int cnt; /* a byte count of printed chars */ int len; /* length of error message */ char *lenptr; /* where to put the length */ cmd.err = 1; rtastat.nsqlerr++; if (rtadbg.sqlerr) rtalog(filename, lineno, Er_Bad_SQL, cmd.sqlcmd); /* Make sure we have enough space for the output. Current #defines limit the maximum message to less than 100 bytes */ if (*cmd.nout < 100) { return; /* not much else we can do... */ } /* We want to overwrite any output so far. We do this by */ /* pointing the output buffer back to its original value. */ cmd.out = cmd.errout; /* Reset any output so far */ *(cmd.nout) = cmd.nerrout; /* The format of the error message is 'E', int32 for length, a series of parameters including 'S'everity, error 'C'ode, and 'M'essage. Parameters are delimited with nulls and there is an extra null at the end to indicate that there are no more parameters. . */ *cmd.out++ = 'E'; lenptr = cmd.out; /* msg length goes here */ cmd.out += 4; /* skip over length for now */ ad_str(&(cmd.out), "SERROR", 6); /* severity code */ *cmd.out++ = (char) 0; ad_str(&(cmd.out), "C42601", 6); /* error code (syntax error) */ *cmd.out++ = (char) 0; *cmd.out++ = 'M'; cnt = snprintf(cmd.out, *(cmd.nout), fmt, arg); cmd.out += cnt; cmd.out++; /* to include the NULL */ *cmd.out++ = (char) 0; /* terminate param list */ len = (int) (cmd.out - cmd.errout) - 1; /* -1 to exclude E */ ad_int4(&lenptr, len); *cmd.nout -= (int) (cmd.out - cmd.errout); return; } /*************************************************************** * do_update(): - Execute the SQL update command in the * sql_cmd structure. * * Input: A buffer to store the output * The number of free bytes in the buffer * Output: The number of free bytes in the buffer * Effects: Lots. This is where the write callbacks * are executed. ***************************************************************/ void do_update(char *buf, int *nbuf) { int sr; /* the Size of each Row in the table */ int rx; /* Row indeX in for() loop */ int wx; /* Where clause indeX in for loop */ void *pr; /* Pointer to the row in the table/column */ void *pd; /* Pointer to the Data in the table/column */ void *poldrow; /* Pointer to copy of row before update */ llong cmp; /* has actual relation of col and val */ int dor; /* DO Row == 1 if we should update row */ char *startbuf; /* used to compute response length */ int cx; /* Column index while building Data pkt */ int n; /* number of chars printed in sprintf() */ int nru = 0; /* =# rows updated */ int svt = 0; /* Save table if == 1 */ char *tmark; /* Address of U in "CUPDATE" if success */ startbuf = buf; /* We loop through all rows in the table in question applying the WHERE condition. If a row matches we update the appropriate columns and call any write callbacks */ sr = cmd.ptbl->rowlen; rx = 0; if (cmd.ptbl->iterator) pr = (cmd.ptbl->iterator) ((void *) NULL, cmd.ptbl->it_info, rx); else pr = cmd.ptbl->address; /* for each row ..... */ while (pr) { dor = 1; for (wx = 0; wx < cmd.nwhrcols; wx++) { /* The WHERE clause ...... execute read callback (if defined) on row * the call back is expected to fill in the data */ if (cmd.pwhr[wx]->readcb) { if ((cmd.pwhr[wx]->readcb) (cmd.tbl, cmd.whrcols[wx], cmd.sqlcmd, pr, rx) != 0) { send_error(LOC, E_BADTRIG, cmd.whrcols[wx]); return; } } /* compute pointer to actual data */ pd = (char *)pr + cmd.pwhr[wx]->offset; /* do comparison based on column data type */ switch (cmd.pwhr[wx]->type) { case RTA_STR: cmp = strncmp((char *) pd, cmd.whrvals[wx], cmd.pwhr[wx]->length); break; case RTA_PSTR: cmp = strcmp(*(char **) pd, cmd.whrvals[wx]); break; case RTA_INT: cmp = *((int *) pd) - cmd.whrints[wx]; break; case RTA_PINT: cmp = **((int **) pd) - cmd.whrints[wx]; break; case RTA_LONG: cmp = *((llong *) pd) - cmd.whrlngs[wx]; break; case RTA_PLONG: cmp = **((llong **) pd) - cmd.whrlngs[wx]; break; case RTA_FLOAT: cmp = *((float *) pd) - cmd.whrflot[wx]; break; case RTA_PFLOAT: cmp = **((float **) pd) - cmd.whrflot[wx]; break; case RTA_PTR: cmp = *((int *) pd) - cmd.whrints[wx]; break; default: cmp = 1; /* assume no match */ break; } if (!(((cmp == 0) && (cmd.whrrel[wx] == RTA_EQ || cmd.whrrel[wx] == RTA_GE || cmd.whrrel[wx] == RTA_LE)) || ((cmp != 0) && (cmd.whrrel[wx] == RTA_NE)) || ((cmp < 0) && (cmd.whrrel[wx] == RTA_LE || cmd.whrrel[wx] == RTA_LT)) || ((cmp > 0) && (cmd.whrrel[wx] == RTA_GE || cmd.whrrel[wx] == RTA_GT)))) { dor = 0; break; } } if (dor && cmd.offset) cmd.offset--; else if (dor) { /* DO Row */ /* if we get here, we've passed the WHERE clause and the OFFSET. Check for LIMIT filtering */ if (cmd.limit <= 0) { break; } /* At this point we have a row which passed the * WHERE clause, is greater than OFFSET and less * than LIMIT. So update it! */ /* Save the data from the old row for use in the write callback */ poldrow = malloc(cmd.ptbl->rowlen); if (poldrow) memcpy(poldrow, pr, cmd.ptbl->rowlen); else { rtalog(LOC, Er_No_Mem); return; } /* Scan the columns doing updates as needed */ for (cx = 0; cx < cmd.ncols; cx++) { /* compute pointer to actual data */ pd = (char *)pr + cmd.pcol[cx]->offset; switch ((cmd.pcol[cx])->type) { case RTA_STR: strncpy((char *) pd, cmd.updvals[cx], cmd.pcol[cx]->length); break; case RTA_PSTR: strncpy(*(char **) pd, cmd.updvals[cx], cmd.pcol[cx]->length); break; case RTA_INT: *((int *) pd) = cmd.updints[cx]; break; case RTA_PINT: **((int **) pd) = cmd.updints[cx]; break; case RTA_LONG: *((llong *) pd) = cmd.updlngs[cx]; break; case RTA_PLONG: **((llong **) pd) = cmd.updlngs[cx]; break; case RTA_PTR: /* works only if INT and PTR are same size */ *((int *) pd) = cmd.updints[cx]; break; case RTA_FLOAT: *((float *) pd) = cmd.updflot[cx]; break; case RTA_PFLOAT: **((float **) pd) = cmd.updflot[cx]; break; } if (cmd.pcol[cx]->flags & RTA_DISKSAVE) svt = 1; } /* We call the write callbacks after all of the columns have been updated. */ for (cx = 0; cx < cmd.ncols; cx++) { /* execute write callback (if defined) on row. callback will perform post processing on row and return zero on success */ if (cmd.pcol[cx]->writecb) { if ((cmd.pcol[cx]->writecb) (cmd.tbl, cmd.pcol[cx]->name, cmd.sqlcmd, pr, rx, poldrow) != 0) { /* restore row from saved image of it */ memcpy(pr, poldrow, cmd.ptbl->rowlen); free(poldrow); send_error(LOC, E_BADTRIG, cmd.pcol[cx]->name); return; } } } if (poldrow) /* free the image of the last row */ free(poldrow); cmd.limit--; /* decrement row limit count */ nru++; } rx++; if (cmd.ptbl->iterator) pr = (cmd.ptbl->iterator) (pr, cmd.ptbl->it_info, rx); else { if (rx >= cmd.ptbl->nrows) pr = (void *) NULL; else pr = (char *)cmd.ptbl->address + (rx * sr); } } /* Save the table to disk if needed */ if (svt && cmd.ptbl->savefile && strlen(cmd.ptbl->savefile)) rta_save(cmd.ptbl, cmd.ptbl->savefile); /* Send the update complete message */ *buf++ = 'C'; tmark = buf; /* Save length location */ buf += 4; ad_str(&buf, "UPDATE", 6); n = sprintf(buf, " %d", nru); /* # rows affected */ buf += n; *buf++ = 0x00; ad_int4(&tmark, (buf - tmark)); *nbuf -= (int) (buf - startbuf); /* Log SQL if trace is on. (+7 skips over 'UPDATE ') */ if (rtadbg.trace) rtalog(LOC, Er_Trace_SQL, cmd.sqlcmd, (tmark + 7)); } /*************************************************************** * ad_str(): - Add a string to the output buffer. Includes a * NULL to terminate the string. * * Input: A **char to the buffer and the string and a length * Output: void * Effects: Increments the **char to point to the next * available space in the buffer. ***************************************************************/ void ad_str(char **pbuf, char *instr, int count) { strncpy(*pbuf, instr, count); *pbuf += count; **pbuf = (char) 0; } /*************************************************************** * ad_int2(): - Add a 2 byte integer to the output buffer * * Input: A **char to the buffer and the integer * Output: void * Effects: Increments the **char to point to the next * available space in the buffer. ***************************************************************/ void ad_int2(char **pbuf, int inint) { **pbuf = (char) ((inint >> 8) & 0x00FF); (*pbuf)++; **pbuf = (char) (inint & 0x00FF); (*pbuf)++; } /*************************************************************** * ad_int4(): - Add a 4 byte integer to the output buffer * * Input: A **char to the buffer and the integer * Output: void * Effects: Increments the **char to point to the next * available space in the buffer. ***************************************************************/ void ad_int4(char **pbuf, int inint) { **pbuf = (char) ((inint >> 24) & 0x00FF); (*pbuf)++; **pbuf = (char) ((inint >> 16) & 0x00FF); (*pbuf)++; **pbuf = (char) ((inint >> 8) & 0x00FF); (*pbuf)++; **pbuf = (char) (inint & 0x00FF); (*pbuf)++; } /*************************************************************** * rtalog(): - Sends debug log messages to syslog() and stderr. * * Output: void * Effects: Increments the **char to point to the next * available space in the buffer. ***************************************************************/ void rtalog(char *fname, /* error detected in file... */ int linen, /* error detected at line # */ char *format, ...) { /* printf format string */ extern struct RtaDbg rtadbg; va_list ap; char *s1; /* first optional argument */ char *s2; /* second optional argument */ char *sptr; /* used to look for %s */ s1 = (char *) 0; s2 = (char *) 0; /* Get the optional parameters if any */ va_start(ap, format); sptr = strstr(format, "%s"); if (sptr) { s1 = va_arg(ap, char *); sptr++; if (sptr) s2 = va_arg(ap, char *); } va_end(ap); /* Send to syslog() if so configured */ if (rtadbg.target == 1 || rtadbg.target == 3) syslog(rtadbg.priority, format, fname, linen, s1, s2); /* Send to stderr if so configured */ if (rtadbg.target == 2 || rtadbg.target == 3) { fprintf(stderr, format, fname, linen, s1, s2); fprintf(stderr, "\n"); } } Node-path: src/do_sql.h Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 5376 Text-content-md5: 8551c4b2c7d405132ac774eb03314feb Content-length: 5386 PROPS-END /*************************************************************** * Run Time Access * Copyright (C) 2003-2006 Robert W Smith (bsmith@linuxtoys.org) * * This program is distributed under the terms of the GNU LGPL. * See the file COPYING file. **************************************************************/ /*************************************************************** * do_sql.h **************************************************************/ #ifndef DO_SQL_H #define DO_SQL_H 1 #include "rta.h" /* types of SQL statements recognized */ #define RTA_SELECT 0 #define RTA_UPDATE 1 /* types of relations allowed in WHERE */ #define RTA_EQ 0 #define RTA_NE 1 #define RTA_GT 2 #define RTA_LT 3 #define RTA_GE 4 #define RTA_LE 5 /* Defines for the meta tables. The table of tables must always be table #0, and the table of columns must always be table #1. */ #define RTA_TABLES 0 #define RTA_COLUMNS 1 /* Used to remove a few characters from dbg() lines */ #define LOC __FILE__,__LINE__ /* Maximum number of characters in printed data type */ #define MX_INT_STRING (12) #define MX_LONG_STRING (24) #define MX_FLOT_STRING (24) /* Max # strings in our private stack for yacc */ #define MXPARSESTR ((NCMDCOLS *2) + 4) /** ************************************************************ * This structure contains/encodes the parsed SQL command from * one of the UI or client interfaces. * This structure is filled in by the yacc parser. If the parse * is successful, the completed structure is passed to do_sql() * for execution. * * The 'command' is just the type of SQL command. * The 'cols' field is a list of names from the "SELECT cols" * or from the "UPDATE col=X [,...]". * The 'vals' field is a list of the strings "X" in an UPDATE. * The 'tbl' field has the name of the table in use. * The 'whrcols' and 'whrvals' fields are similar to the cols * and vals fields. * Note that cols, vals, whrcols, and whrvals in the structure * below point to alloc()'ed memory and must be freed when done. **************************************************************/ struct Sql_Cmd { char *sqlcmd; /* points to text of SQL command */ int command; /* RTA_SELECT or UPDATE */ char *tbl; /* the table in question */ TBLDEF *ptbl; /* pointer to table in TBLDEFS */ int itbl; /* Index of table in Tbl */ int ncols; /* count of columns to display/update */ char *cols[NCMDCOLS]; /* col to display/update */ COLDEF *pcol[NCMDCOLS]; /* pointers to cols in COLDEFS */ char *updvals[NCMDCOLS]; /* values for column updates */ int updints[NCMDCOLS]; /* integer values for updates */ llong updlngs[NCMDCOLS]; /* long values for updates */ float updflot[NCMDCOLS]; /* float values for updates */ int nwhrcols; /* count of columns in where clause */ char *whrcols[NCMDCOLS]; /* cols in where */ int whrrel[NCMDCOLS]; /* relation (EQ, GT...) in where */ COLDEF *pwhr[NCMDCOLS]; /* pointers to Wcols in COLDEFS */ char *whrvals[NCMDCOLS]; /* values in the where clause */ int whrints[NCMDCOLS]; /* integer values of whrvals[] */ llong whrlngs[NCMDCOLS]; /* long values of whrvals[] */ float whrflot[NCMDCOLS]; /* float values of whrvals[] */ int limit; /* max num rows to output, 0=no_limit */ int offset; /* scan past this # rows before output */ char *out; /* put command response here */ int *nout; /* I/O number free bytes at 'out' */ char *errout; /* ==out at start. But for err msgs */ int nerrout; /* ==nout at start. But for err msgs */ int err; /* set =1 if error in SQL parse */ int nlineout; /* #bytes in SELECT row response */ }; /* Define the debug config structure */ struct RtaDbg { int syserr; /* !=0 to log system errors */ int rtaerr; /* !=0 to log rta errors */ int sqlerr; /* !=0 to log SQL errors */ int trace; /* !=0 to log SQL commands */ int target; /* 0=off, 1=syslog, 2=stderr, 3=both */ int priority; /* syslog() priority level */ int facility; /* syslog() facility */ char ident[MXDBGIDENT]; /* ident string for syslog() */ }; /* Define the stats structure */ struct RtaStat { llong nsyserr; /* count of failed OS calls. */ llong nrtaerr; /* count of internal rta failures. */ llong nsqlerr; /* count of SQL failures. */ llong nauth; /* count of DB authorizations. */ llong nupdate; /* count of UPDATE requests */ llong nselect; /* count of SELECT requests */ }; /* Forward references */ void do_sql(char *, int *); void send_error(char *, int, char *, char *); void verify_table_name(char *, int *); void verify_select_list(char *, int *); void verify_update_list(char *, int *); void verify_where_list(char *, int *); void do_update(char *, int *); void do_update(char *, int *); int send_row_description(char *, int *); void do_select(char *, int *); void do_call(char *, int *); void ad_str(char **, char *, int); void ad_int2(char **, int); void ad_int4(char **, int); void rtalog(char *, int, char *, ...); #endif Node-path: src/parse.y Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 5339 Text-content-md5: 3c75431ffb135f3de02a05252b77bfec Content-length: 5349 PROPS-END /*************************************************************** * Run Time Access * Copyright (C) 2003-2006 Robert W Smith (bsmith@linuxtoys.org) * * This program is distributed under the terms of the GNU LGPL. * See the file COPYING file. **************************************************************/ /*************************************************************** * parse.y -- Yacc parser for our subset of SQL. **************************************************************/ %{ #include #include "do_sql.h" #define YYSTYPE int /* While we parse the SET and WHERE clause we need someplace * to temporarily store the type of relation */ static int whrrelat; /* We don't want to pass pointers to allocated memory on the */ /* yacc stack, since the memory might not be freed when an */ /* error is detected. Instead, we allocate the memory and */ /* put the pointer into the following table where it is easy */ /* free on error. */ char *parsestr[MXPARSESTR]; static int n; /* temp/scratch integer */ extern struct Sql_Cmd cmd; /* encoded SQL command (a global) */ extern char *yytext; extern int yyleng; extern void yyerror(char *); extern int yylex(); %} %token SELECT %token UPDATE %token FROM %token WHERE %token NAME %token STRING %token INTEGER %token REALNUM %token LIMIT %token OFFSET %token SET %token GARBAGE %token TERMINATOR /* relations for the where clause */ %token EQ %token NE %token GT %token LT %token GE %token LE %left AND %left ',' %% command: select_statement | update_statement | empty_statement ; empty_statement: TERMINATOR { YYABORT; } ; select_statement: SELECT column_list FROM table_name where_clause limit_clause TERMINATOR { cmd.command = RTA_SELECT; YYACCEPT; } ; column_list: NAME { cmd.cols[cmd.ncols] = parsestr[(int) $1]; parsestr[(int) $1] = (char *) NULL; cmd.ncols++; } | column_list ',' NAME { cmd.cols[cmd.ncols] = parsestr[(int) $3]; parsestr[(int) $3] = (char *) NULL; cmd.ncols++; if (cmd.ncols > NCMDCOLS) { /* too many columns in list */ send_error(LOC, E_BADPARSE); } } ; table_name: NAME { cmd.tbl = parsestr[(int) $1]; parsestr[(int) $1] = (char *) NULL; } ; where_clause: /* empty, optional */ | WHERE test_condition ; test_condition: '(' test_condition ')' | test_condition AND test_condition | NAME relation literal { n = cmd.nwhrcols; cmd.whrcols[n] = parsestr[(int) $1]; parsestr[(int) $1] = (char *) NULL; cmd.whrrel[n] = whrrelat; cmd.whrvals[n] = parsestr[(int) $3]; parsestr[(int) $3] = (char *) NULL; cmd.nwhrcols++; if (cmd.nwhrcols > NCMDCOLS) { /* too many columns in list */ send_error(LOC, E_BADPARSE); } } ; relation: EQ { whrrelat = RTA_EQ; } | NE { whrrelat = RTA_NE; } | GT { whrrelat = RTA_GT; } | LT { whrrelat = RTA_LT; } | GE { whrrelat = RTA_GE; } | LE { whrrelat = RTA_LE; } ; limit_clause: /* empty, optional */ | LIMIT INTEGER { cmd.limit = atoi(parsestr[(int) $2]); free(parsestr[(int) $2]); parsestr[(int) $2] = (char *) NULL; } | LIMIT INTEGER OFFSET INTEGER { cmd.limit = atoi(parsestr[(int) $2]); free(parsestr[(int) $2]); parsestr[(int) $2] = (char *) NULL; cmd.offset = atoi(parsestr[(int) $4]); free(parsestr[(int) $4]); parsestr[(int) $4] = (char *) NULL; } ; update_statement: UPDATE NAME SET set_list where_clause limit_clause TERMINATOR { cmd.command = RTA_UPDATE; cmd.tbl = parsestr[(int) $2]; parsestr[(int) $2] = (char *) NULL; YYACCEPT; } ; set_list: set_list ',' set_list | NAME EQ literal { n = cmd.ncols; cmd.cols[n] = parsestr[(int) $1]; parsestr[(int) $1] = (char *) NULL; cmd.updvals[n] = parsestr[(int) $3]; parsestr[(int) $3] = (char *) NULL; cmd.ncols++; if (cmd.ncols > NCMDCOLS) { /* too many columns in list */ send_error(LOC, E_BADPARSE); } } ; literal: NAME | STRING | INTEGER | REALNUM ; %% /*************************************************************** * dosql_init(): - Set up data structures prior to parse of * an SQL command. * * Input: None. * Output: None. * Affects: structure cmd is initialized ***************************************************************/ void dosql_init() { int i; for (i=0; i /* for PATH_MAX */ /** Maximum number of tables allowed in the system. * Your data base may not contain more than this number * of tables. */ #define MX_TBL (500) /** Maximum number of columns allowed in the system. * Your data base may not contain more than this number * of columns. */ #define MX_COL (2500) /** Maximum number of characters in a column name, table * name, and in help. See TBLDEF and COLDEF below. */ #define MXCOLNAME (30) #define MXTBLNAME (30) #define MXHELPSTR (1000) #define MXFILENAME PATH_MAX /** Maximum number of characters in the 'ident' field of * the openlog() call. See the rta_dbg table below. */ #define MXDBGIDENT (20) /** Maximum line size. SQL commands in save files may * contain no more than MX_LN_SZ characters. Lines with * more than MX_LN_SZ characters are silently truncated * to MX_LN_SZ characters. */ #define MX_LN_SZ (1500) /* Maximum number of columns allowed in a table */ #define NCMDCOLS (40) /*************************************************************** * - Data Structures: * Each column and table in the data base must be described * in a data structure. Here are the data structures and * associated defines to describe tables and columns. **************************************************************/ /** The column definition (COLDEF) structure describes * one column of a table. A table description has an * array of COLDEFs to describe the columns in the * table. */ typedef struct { /** The name of the table that has this column. */ char *table; /** The name of the column. Must be at most MXCOLNAME * characters in length and must be unique within a * table. The same column name may be used in more * than one table. */ char *name; /** The data type of the column. Must be int, long, * string, pointer to void, pointer to int, pointer * to long, or pointer to string. The DB types are * defined immediately following this structure. */ int type; /** The number of bytes in the string if the above * type is RTA_STR or RTA_PSTR. The length includes * the null at the end of the string. */ int length; /** Number of bytes from the start of the structure to * this column. For example, a structure with an int, * a 20 character string, and a long, would have the * offset of the long set to 24. Use of the function * offsetof() is encouraged. If you have structure * members that do not start on word boundaries and * you do not want to use offsetof(), then consider * using -fpack-struct with gcc. */ int offset; /** Boolean flags which describe attributes of the * columns. The flags are defined after this * structure and include a "read-only" flag and a * flag to indicate that updates to this column * should cause a table save. (See table savefile * described below.) */ int flags; /** Read callback. This routine is called before the * column value is used. Input values include the * table name, the column name, the input SQL command, * a pointer to the row affected, and the (zero indexed) * row number for the row that is being read. It * returns a 0 on success and non-zero on failure. * This routine is called *each* time the column is * read so the following would produce two calls: * SELECT intime FROM inns WHERE intime >= 100; */ int (*readcb) (char *tbl, char *column, char *SQL, void *pr, int row_num); /** Write callback. This routine is called after an * UPDATE in which the column is written. Input values * include the table name, the column name, the SQL * command, a pointer to the row affected, the (zero * indexed) row number of the modified row, and a pointer * to a copy of the row before any modifications. See the * callback section below. * This routine is called only once after all column * updates have occurred. For example, if there were * a write callback attached to the addr column, the * following SQL statement would cause the execution * of the write callback once after both mask and addr * have been written: * UPDATE ethers SET mask="255.255.255.0", addr = \ * "192.168.1.10" WHERE name = "eth1"; * The callback is called for each row modified. * A callback returns zero on success and non-zero on * failure. On failure, the table's row is restored * to it's initial values and an SQL error is returned * to the client. The error is TRIGGERED ACTION EXCEPTION */ int (*writecb) (char *tbl, char *column, char *SQL, void *pr, int row_num, void *poldrow); /** A brief description of the column. This should * include the meaning of the data in the column, the * limits, if any, and the default values. Include * a brief description of the side effects of changes. * This field is particularly important for tables * which are part of the "boundary" between the UI * developers and the application programmers. */ char *help; } COLDEF; /** The data types. * String refers to an array of char. The 'length' of * column must contain the number of bytes in the array. */ #define RTA_STR 0 /** Pointer to void. Use for generic pointers */ #define RTA_PTR 1 /** Integer. This is the compiler/architecture native * integer. On Linux/gcc/Pentium an integer is 32 bits. */ #define RTA_INT 2 /** Long. This is the compiler/architecture native * long long. On Linux/gcc/Pentium a llong is 64 * bits. Define 'llong' to suit your needs. */ typedef long long llong; #define RTA_LONG 3 /** Pointer to string. Pointer to an array of char, or * a (**char). Note that the column length should be * the number of bytes in the string, not sizeof(char *). */ #define RTA_PSTR 4 /** Pointers to int and long. */ #define RTA_PINT 5 #define RTA_PLONG 6 /** Float and pointer to float */ #define RTA_FLOAT 7 #define RTA_PFLOAT 8 #define MXCOLTYPE (RTA_PFLOAT) /** The boolean flags. * If the disksave bit is set any writes to the column * causes the table to be saved to the "savefile". See * savefile described in the TBLDEF section below. */ #define RTA_DISKSAVE (1<<0) /** If the readonly flag is set, any writes to the * column will fail and a debug log message will be * sent. (For unit test you may find it very handy to * leave this bit clear to get better test coverage of * the corner cases.) */ #define RTA_READONLY (1<<1) /** The table definition (TBLDEF) structure describes * a table and is passed into the DB system by the * rta_add_table() subroutine. */ typedef struct { /** The name of the table. Must be less than than * MXTLBNAME characters in length. Must be unique * within the DB. */ char *name; /** Address of the first element of the first row of * the array of structs that make up the table. */ void *address; /** The number of bytes in each row of the table. * This is usually a sizeof() of the structure * associated with the table. (The idea is that we * can get to data element E in row R with ... * data = *(address + (R * rowlen) + offset(E)) */ int rowlen; /** Number of rows in the table. */ int nrows; /** An 'iterator' on the rows of the data. This is * useful if you want to have a linked list (or other * arrangement) instead of a linear array of struct. * Your iterator should return a pointer to the first * row when the input is NULL and should return a NULL * when asked for the row after the last row. The rowid * is the number of the row desired. */ void *(*iterator) (void *cur_row, void *it_info, int rowid); /** A pointer to any kind of information that the * caller wants returned with each iterator call. * For example, you may wish to have one iterator * for all of your linked lists. You could pass in * a unique identifier for each table so the function * can handle each one as appropriate. */ void *it_info; /** An array of COLDEF structures which describe each * column in the table. These must be in statically * allocated memory since the rta system references * them while running. */ COLDEF *cols; /** The number of columns in the table. That is, the * number of COLDEFs defined by 'cols'. */ int ncol; /** Save file. Path and name of a file which stores * the non-volatile part of the table. The file has * all of the UPDATE statements needed to rebuild the * table. The file is rewritten in its entirety each * time a 'savetodisk' column is updated. No file * save is attempted if the savefile is blank. */ char *savefile; /** Help text. A description of the table, how it is * used, and what its intent is. A brief note to * describe how it relate to other parts of the system * and description of important callbacks is nice * thing to include here. */ char *help; } TBLDEF; /*************************************************************** * - Subroutines * Here is a summary of the few routines in the rta API: * dbcommand() - I/F to Postgres clients * rta_add_table() - add a table and its columns to the DB * SQL_string() - execute an SQL statement in the DB * rta_save() - save a table to a file * rta_load() - load a table from a file * **************************************************************/ /** ************************************************************ * dbcommand(): - Depacketize and execute Postgres commands. * * The main application accepts TCP connections from Postgres * clients and passes the stream of bytes (encoded SQL requests) * from the client into the rta system via this routine. If the * input buffer contains a complete command, it is executed, nin * is decrement by the number of bytes consumed, and RTA_SUCCESS * is returned. If there is not a complete command, RTA_NOCMD * is returned and no bytes are removed from the input buffer. * If a command is executed, the results are encoded into the * Postgres protocol and placed in the output buffer. When the * routine is called the input variable, nout, has the number of * free bytes available in the output buffer, out. When the * routine returns nout has been decremented by the size of the * response placed in the output buffer. An error message is * generated if the number of available bytes in the output * buffer is too small to hold the response from the SQL command. * * Input: cmd - the buffer with the Postgres packet * nin - on entry, the number of bytes in 'cmd', * on exit, the number of bytes remaining in cmd * 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_CLOSE - client requests an orderly close * RTA_NOBUF - insufficient output buffer space **************************************************************/ int dbcommand(char *, int *, char *, int *); /** ************************************************************ * rta_add_table(): - Register a table for inclusion in the * DB interface. Adding a table allows external Postgres * clients access to the table's content. * Note that the TBLDEF structure must be statically * allocated. The DB system keeps just the pointer to the table * and does not copy the information. This means that you can * change the contents of the table definition by changing the * contents of the TBLDEF structure. This might be useful if * you need to allocate more memory for the table and change its * row count and address. * An error is returned if another table by the same name * already exists in the DB or if the table is defined without * any columns. * If a 'savefile' is specified, it is loaded. (See the * rta_load() command below for more details.) * * Input: ptbl - pointer to the TBLDEF to add * Return: RTA_SUCCESS - table added * RTA_ERROR - error **************************************************************/ int rta_add_table(TBLDEF *); /** ************************************************************ * SQL_string(): - Execute single SQL command * * Executes the SQL command placed in the null-terminated string, * cmd. The results are encoded into the Postgres protocol and * placed in the output buffer. When the routine is called the * input variable, nout, has the number of free bytes available * in the output buffer, out. When the routine returns nout has * been decremented by the size of the response placed in the * output buffer. An error message is generated if the number * of available bytes in the output buffer is too small to hold * the response from the SQL command. * This routine may be most useful when updating a table * value in order to invoke the write callbacks. (The output * buffer has the results encoded in the Postgres protocol and * might not be too useful directly.) * * Input: cmd - the buffer with the SQL command, * nin - number of bytes in cmd, * 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: **************************************************************/ void SQL_string(char *, int, char *, int *); /** ************************************************************ * rta_config_dir(): - sets the default path to the savefiles. * * The string pointed to by configdir is saved and is prepended * to the savefile names for tables with savefiles. This call * should be used before loading your application tables. It * is intended to make it simpler for applications which let * the user specify an configuration directory on the command * line. * If the savefile uses an absolute path (starting with '/') * it is not prepended with the configuration directory. * * Return: RTA_SUCCESS - config path set * RTA_ERROR - error, (not a valid directory?) **************************************************************/ int rta_config_dir(char *configdir); /** ************************************************************ * rta_save(): - Save table to file. Saves all "savetodisk" * columns to the path/file specified. Only savetodisk columns * are saved. The resultant file is a list of UPDATE commands * containing the desired data. There is one UPDATE command for * each row in the table. * This routine tries to minimize exposure to corrupted * save files by opening a temp file in the same directory as * the target file. The data is saved to the temp file and the * system call rename() is called to atomically move the temp * to the save file. Errors are generated if rta_save can not * open the temp file or is unable to rename() it. * As a general warning, note that any disk I/O can cause * a program to block briefly and so saving and loading tables * might cause your program to block. * * Input: ptbl - pointer to the TBLDEF structure for the * table to save * fname - null terminated string with the path and * file name for the stored data. * Return: RTA_SUCCESS - table saved * RTA_ERROR - some kind of error **************************************************************/ int rta_save(TBLDEF *, char *); /** ************************************************************ * rta_load(): - Load a table from a file of UPDATE commands. * The file format is a series of UPDATE commands with one * command per line. Any write callbacks are executed as the * update occurs. * * Input: ptbl - pointer to the table to be loaded * fname - string with name of the load file * * Return: RTA_SUCCESS - table loaded * RTA_ERROR - could not open the file specified **************************************************************/ int rta_load(TBLDEF *, char *); /* successfully executed request or command */ #define RTA_SUCCESS (0) /* input did not have a full command */ #define RTA_NOCMD (1) /* encountered an internal error */ #define RTA_ERROR (2) /* DB client requests a session close */ #define RTA_CLOSE (3) /* Insufficient output buffer space */ #define RTA_NOBUF (4) /** ************************************************************ * - rta UPDATE and SELECT syntax * rta IS AN API, *NOT* A DATABASE! * Neither the rta UPDATE nor the rta SELECT adhere to the * Postgres equivalents. Joins are not allowed, and the WHERE * clause supports only the AND relation. There are no locks * or transactions. * * SELECT: * SELECT column_list FROM table [where_clause] [limit_clause] * * SELECT supports multiple columns, '*', LIMIT, and OFFSET. * At most MXCMDCOLS columns can be specified in the select list * or in the WHERE clause. LIMIT restricts the number of rows * returned to the number specified. OFFSET skips the number of * rows specified and begins output with the next row. * 'column_list' is a '*' or 'column_name [, column_name ...]'. * 'where_clause' is 'col_name = value [AND col_name = value ..]' * in which all col=val pairs must match for a row to match. * LIMIT and OFFSET are very useful to prevent a buffer * overflow on the output buffer of dbcommand(). They are also * very useful for web based user interfaces in which viewing * the data a page-at-a-time is desirable. * Column and table names are case sensitive and may not be * one of the reserved words. The reserved words are: AND, FROM, * LIMIT, OFFSET, SELECT, SET, UPDATE, and WHERE. Reserved * words are *not* case sensitive. You may use lower case * reserved words in your SQL statements if you wish. * Comparison operator in the WHERE clause include =, >=, * <=, >, and <. * You can use a reserved word, like OFFSET, as a column name * but you will need to quote it whenever you reference it in an * SQL command (SELECT "offset" FROM tunings ...). Strings * may contain any of the !@#$%^&*()_+-={}[]\|:;<>?,./~` * characters. If a string contains a double quote, use a * single quote to wrap it (eg 'The sign says "Hi mom!"'), and * use double quotes to wrap string with embedded single quotes. * * Examples: * SELECT * FROM rta_tables * * SELECT destIP FROM conns WHERE fd != 0 * * SELECT destIP FROM conns WHERE fd != 0 AND lport = 80 * * SELECT destIP, destPort FROM conns \ * WHERE fd != 0 \ * LIMIT 100 OFFSET 0 * * SELECT destIP, destPort FROM conns \ * WHERE fd != 0 \ * LIMIT 100 OFFSET 0 * * * UPDATE: * UPDATE table SET update_list [where_clause] [limit_clause] * * UPDATE writes values into a table. The update_list is of * the form 'col_name = val [, col_name = val ...]. The WHERE * and LIMIT clauses are as described above. * An update invokes write callbacks on the affected columns. * All data in the row is written before the callbacks are * called. * The LIMIT clause for updates is not standard Postgres SQL, * but can be really useful for stepping through a table one row * at a time. To change only the n'th row of a table, use a * limit clause like 'LIMIT 1 OFFSET n' (n is zero-indexed). * * Examples: * UPDATE conn SET lport = 0; * * UPDATE ethers SET mask = "255.255.255.0", \ * addr = "192.168.1.10" \ * WHERE name = "eth0" * * UPDATE conn SET usecount = 0 WHERE fd != 0 AND lport = 21 * **************************************************************/ /** ************************************************************ * - Internal DB tables * rta has four tables visible to the application: * rta_tables: - a table of all tables in the DB * rta_columns: - a table of all columns in the DB * rta_logconfig: - controls what gets logged from rta * rta_stats: - simple usage and error statistics * * The rta_tables table gives SQL access to all internal and * registered tables. The data in the table is exactly that of * the TBLDEF structures registered with rta_add_table(). This * table is used for the generic table viewer and table editor * applications used mostly for application debugging. The * columns of rta_tables are: * name - the name of the table * address - the start address of the table in memory * rowlen - number of bytes in each row of the table * nrows - number of rows in the table * cols - pointer to array of column definitions * ncol - number of columns in the table * iterator - subroutine to advance from one row to next * it_info - transparent data for the iterator * savefile - the file used to store non-volatile columns * help - a description of the table * * The rta_columns table has the column definitions of all * columns in the DB. The data in the table is exactly that of * the COLDEF structures registered with rta_add_table(). This * table is used for the generic table viewer and table editor * applications used mostly for application debugging. The * columns of rta_columns are: * table - the name of the column's table * name - name of the column * type - column's data type * length - number of bytes columns data type * offset - number of bytes from start of structure * flags - Bit field for 'read-only' and 'savetodisk' * readcb - pointer to subroutine called before reads * writecb - pointer to subroutine called after writes * help - a description of the column * * The rta_dbgconfig table controls which errors generate * debug log messages. See the logging section below for the * exact mapping. The rta package generates no user level log * messages, only debug messages. All of the fields in this * table are volatile. You will need to set the values in your * main program to make them seem persistent. (Try something * like "SQL_string("UPDATE rta_dbgconfig SET dbg ....").) * The columns of rta_dbgconfig are: * syserr - integer, 0 means no log, 1 means log. * This logs OS call errors like malloc() * failures. Default is 1. * rtaerr - integer, 0 means no log, 1 means log. * Enables logging of errors internal to the * rta package itself. Default is 1. * sqlerr - integer, 0 means no log, 1 means log. * Log any SQL request which generates an * error reply. Error replies occur if an SQL * request is malformed or if it requests a * non-existent table or column. Default is 1. * (SQL errors are usually client programming * errors.) * trace - integer, 0 means no log, 1 means log all * SQL requests. Default is 0. * target - 0: disable all debug logging * 1: log debug messages to syslog() * 2: log debug messages to stderr * 3: log to both syslog() and stderr * The default is 1. Setting the facility * causes a close and an open of syslog(). * priority - integer. Syslog() requires a priority as * part of all log messages. This specifies * the priority to use when sending rta debug * messages. Changes to this do not take * effect until dbg_target is updated. * 0: LOG_EMERG * 1: LOG_ALERT * 2: LOG_CRIT * 3: LOG_ERR * 4: LOG_WARNING * 5: LOG_NOTICE * 6: LOG_INFO * 7: LOG_DEBUG * Default is 3. * facility - integer. Syslog() requires a facility as * part of all log messages. This specifies * the facility to use when sending rta debug * messages. It is best to use the defines in * .../sys/syslog.h to set this. The default * is LOG_USER. Changes to this do not take * effect until dbg_target is updated. * ident - string. Syslog() requires an 'ident' string as * part of all log messages. This specifies * the ident string to use when sending rta debug * messages. This is normally set to the process * or command name. The default is "rta". Changes * to this do not take effect until dbg_target * is updated. This can be at most MXDBGIDENT * characters in length. * * The rta_stat table contains usage and error statistics * which might be of interest to developers. All fields are * of type long, are read-only, and are set to zero by the * first call to rta_add_table. The columns of rta_stats are: * nsyserr - count of failed OS calls. * nrtaerr - count of internal rta failures. * nsqlerr - count of SQL failures. * nauth - count of authorizations. (==#connections) * nupdate - count of UPDATE or file write requests * nselect - count of SELECT or file read requests * **************************************************************/ /** ************************************************************ * - List of all messages * There are two types of error messages available in the * rta package. The first type is the error messages returned * as part of an SQL request. The messages of this type are: * * 1) "ERROR: Relation '%s' does not exist" * This reply indicates that a table requested in a SELECT * UPDATE, or where clause does not exist. The %s is * replaced by the name of the requested table. * 2) "ERROR: Attribute '%s' not found" * This reply indicates that a column requested in a SELECT * UPDATE, or where clause does not exist. The %s is * replaced by the name of the requested column. * 3) "ERROR: SQL parse error" * This reply indicates a mal-formed SQL request or a * mis-match in the types of data in a where clause or in * an update list. * 4) "ERROR: Output buffer full" * This reply indicates that the size of the response to * a request exceeds the size of the output buffer. See * dbcommand() and the 'out' and 'nout' parameters. This * error can be avoided with a large enough output buffer * or, preferably, with the use of LIMIT and OFFSET. * 5) "ERROR: String too long for '%s' * This reply indicates that an update to a column of type * string or pointer to string would have exceeded the * width of the column. The %s is replaced by the column * name. * 6) "ERROR: Can not update read-only column '%s' * This reply indicates that an attempt to update a column * marked as read-only. The %s is replaced by the column * name. * 7) "ERROR: Trigger failure on column '%s' * This reply indicates that a read or write callback * failed. * * The other type of error messages are internal debug * messages. Debug messages are logged using the standard * syslog() facility available on all Linux systems. The * default syslog "facility" used is LOG_USER but this can be * changed by setting 'facility' in the rta_dbg table. * You are welcome to modify syslogd in order to do post * processing such as generating SNMP traps off these debug * messages. All error messages of this type are send to * syslog() as: "rta[PID]: FILE LINE#: error_message", * where PID, FILE, and LINE# are replaced by the process ID, * the source file name, and the line number where the error * was detected. * Following are the defines used to generate these debug * and error messages. The "%s %d" at the start of each * error string is replaced by the file name and line number * where the error is detected. */ /** "System" errors */ #define Er_No_Mem "%s %d: Can not allocate memory" #define Er_No_Save "%s %d: Table '%s' save failure. Can not open %s" #define Er_No_Load "%s %d: Table '%s' load failure. Can not open %s" /** "RTA" errors */ #define Er_Max_Tbls "%s %d: Too many tables in DB" #define Er_Max_Cols "%s %d: Too many columns in DB" #define Er_Tname_Big "%s %d: Too many characters in table name: %s" #define Er_Cname_Big "%s %d: Too many characters in column name: %s" #define Er_Hname_Big "%s %d: Too many characters in help text: %s" #define Er_Tbl_Dup "%s %d: DB already has table named: %s" #define Er_Col_Dup "%s %d: Table '%s' already has column named: %s" #define Er_Col_Type "%s %d: Column contains an unknown data type: %s" #define Er_Col_Flag "%s %d: Column contains unknown flag data: %s" #define Er_Col_Name "%s %d: Incorrect table in column definition: %s" #define Er_Cmd_Cols "%s %d: Too many columns in table: %s" #define Er_No_Space "%s %d: Not enough buffer space" #define Er_Reserved "%s %d: Table or column is a reserved word: %s" /** "SQL" errors */ #define Er_Bad_SQL "%s %d: SQL parse error: %s" #define Er_Readonly "%s %d: Attempt to update readonly column: %s" /* SQL errors to the front ends */ #define E_NOTABLE "Relation '%s' does not exist" #define E_NOCOLUMN "Attribute '%s' not found" #define E_BADPARSE "SQL parse error","" #define E_BIGSTR "String too long for '%s'" #define E_NOWRITE "Can not update read-only column '%s'" #define E_FULLBUF "Output buffer full","" #define E_BADTRIG "Failed callback on column '%s'" /** "Trace" messages */ #define Er_Trace_SQL "%s %d: SQL command: %s (%s)" /*************************************************************/ /** ************************************************************ * - How to write callback routines * As mentioned above, read callbacks are executed before a * column value is used and write callbacks are called after all * columns have been updated. Both read and write callbacks * return a zero on success and non-zero on failure. Read * callbacks have the following calling parameters: * - char *tblname: the name of the table referenced * - char *colname: the name of the column referenced * - char *sqlcmd: the text of the SQL command * - void *pr; points to affected row in table * - int rowid: the zero-indexed row number of the row * being read or written * * Read callbacks are particularly useful to compute things * like sums and averages; things that aren't worth the effort * compute continuously if it's possible to compute it just * when it is used. The callback should return zero on success. * If a non-zero value is returned, the client gets an error * message, TRIGGERED ACTION EXCEPTION. * * Write callbacks can form the real engine driving the * application. These are most applicable when tied to * configuration changes. Write callbacks are also a useful * place to log configuration changes. Write callbacks have * the same parameters as read callbacks with the addition of * a pointer to a copy of the row before it was modified. * Access to a copy of the unmodified row is useful to detect * actual changes and not just updates with the same value. * The parameters to a write callback are: * - char *tblname: the name of the table referenced * - char *colname: the name of the column referenced * - char *sqlcmd: the text of the SQL command * - void *pr; points to affected row in table * - int rowid: the zero-indexed row number of the row * being read or written * - void *poldrow; pointer to a copy of the row before any * changes were made. * * A return value of zero indicates success. A non-zero value * indicates an error. On error, RTA will restore the table row * using the copy of the unmodified row and returns an SQL error, * TRIGGERED ACTION EXCEPTION, to the user. **************************************************************/ /*************************************************************** * - Future enhancements: * Several enhancements are possible for the rta package: * - printf format string in the column definition * - ability to register more than one trigger per column * - secure login maintaining Postgres compatibility * - IPC and support for shared tables in shared memory * - specify pre or post for the write callback * - table save callback (to save file to flash?) * - execution times for table access, update * - count(*) function * - model output buffer mgmt on zlib to allow output streams * - add a column data type of "table" to allow nested tables * - add internationalization support * - make it thread safe **************************************************************/ #endif Node-path: src/rtatables.c Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 28679 Text-content-md5: ca8beead8b18abea2a1839e477eff68c Content-length: 28689 PROPS-END /*************************************************************** * Run Time Access * Copyright (C) 2003-2006 Robert W Smith (bsmith@linuxtoys.org) * * This program is distributed under the terms of the GNU LGPL. * See the file COPYING file. **************************************************************/ /*************************************************************** * Overview: * The "rta" package provides a Postgres-like API into our * system tables and variables. We need to describe each of our * tables as if it were a data base table. We describe each * table in general in an array of TBLDEF structures with one * structure per table, and each column of each table in an * array of COLDEF structures with one COLDEF structure per * column. **************************************************************/ #include /* for printf prototypes */ #include /* for 'offsetof' */ #include /* for LOG_ERR, LOG_USER */ #include /* for strncmp prototypes */ #include "rta.h" /* for TBLDEF and COLDEF */ #include "do_sql.h" /* for struct Sql_Cmd */ /* Forward reference for read callbacks and iterators */ int restart_syslog(); void *get_next_sysrow(void *, void *, int); /*************************************************************** * We define a table which contains the column definitions of * all columns in the system. This is a pseudo table in that * there is not an array of structures like other tables. * Instead, pointers to each column definition is placed in a * table. This table requires special handling in do_sql.c. * The table definition for "rta_columns" must appear as the * second entry in the array of table definition pointers. **************************************************************/ /* Define the table columns */ COLDEF rta_columnsCols[] = { { "rta_columns", /* table name */ "table", /* column name */ RTA_PSTR, /* type of data */ MXTBLNAME, /* #bytes in col data */ offsetof(COLDEF, table), /* offset 2 col strt */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "The name of the table that this column belongs to."}, { "rta_columns", /* table name */ "name", /* column name */ RTA_PSTR, /* type of data */ MXCOLNAME, /* #bytes in col data */ offsetof(COLDEF, name), /* offset 2 col strt */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "The name of the column. Must be unique within a table " "definition but may be replicated in other tables. The " "maximum string length of the column name is set by " "MXCOLNAME defined in the rta.h file."}, { "rta_columns", /* table name */ "type", /* column name */ RTA_INT, /* type of data */ sizeof(int), /* #bytes in col data */ offsetof(COLDEF, type), /* offset 2 col strt */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "The data type of the column. Types include string, " "integer, long, pointer, pointer to string, pointer to " "integer, and pointer to long. See rta.h for more details."}, { "rta_columns", /* table name */ "length", /* column name */ RTA_INT, /* type of data */ sizeof(int), /* #bytes in col data */ offsetof(COLDEF, length), /* offset 2 col strt */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "The length of the string in bytes if the column data " "type is a string or a pointer to a string."}, { "rta_columns", /* table name */ "noff", /* column name */ RTA_PTR, /* type of data */ sizeof(void *), /* #bytes in col data */ offsetof(COLDEF, offset), /* offset 2 col strt */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "The number of bytes from the start of the structure " "to the member element defined in this entry. Be careful " "in setting the offset with non word-aligned elements like " "single characters. If you do no use offsetof() consider " "using -fpack-struct. (By the way, the column name is " "actually 'offset' but that conflicts with one of the SQL words"}, { "rta_columns", /* table name */ "flags", /* column name */ RTA_INT, /* type of data */ sizeof(int), /* #bytes in col data */ offsetof(COLDEF, flags), /* offset 2 col strt */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "Flags associated with the column include flags to indicate " "read-only status and whether or not the data should be " "included in the save file. See rta.h for the associated " "defines and details."}, { "rta_columns", /* table name */ "readcb", /* column name */ RTA_PTR, /* type of data */ sizeof(void *), /* #bytes in col data */ offsetof(COLDEF, readcb), /* offset 2 col strt */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "A pointer to a function that returns an integer. If " "defined, the function is called before the column is " "read. This function is useful to compute values only " "when needed. A zero is returned by the callback if the " "callback succeeds."}, { "rta_columns", /* table name */ "writecb", /* column name */ RTA_PTR, /* type of data */ sizeof(void *), /* #bytes in col data */ offsetof(COLDEF, writecb), /* offset 2 col strt */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "A pointer to a function that returns and integer. If " "defined, the function is called after an UPDATE command " "modifies the column. All columns in an UPDATE are " "modified before any write callbacks are executed. This " "function is useful to effect changes requested or implied " "by the column definition. The function return a zero on " "success. If a non-zero value is returned, the SQL client " "receives an TRIGGERED ACTION EXCEPTION error."}, { "rta_columns", /* table name */ "help", /* column name */ RTA_PSTR, /* type of data */ MXHELPSTR, /* #bytes in col data */ offsetof(COLDEF, help), /* offset 2 col strt */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "A brief description of the column. Should include " "limits, default value, and a description of how to set " "it. Can contain at most MXHELPSTR characters."}, }; /* Define the table */ TBLDEF rta_columnsTable = { "rta_columns", /* table name */ (void *) 0, /* address of table */ sizeof(COLDEF), /* length of each row */ 0, /* incremented as tables are added */ get_next_sysrow, /* iterator function */ (void *) RTA_COLUMNS, /* iterator callback data */ rta_columnsCols, /* Column definitions */ sizeof(rta_columnsCols) / sizeof(COLDEF), /* # columns */ "", /* save file name */ "The list of all columns in all tables along with their " "attributes." }; /*************************************************************** * We define a table which contains the table definition of all * tables in the system. This is a pseudo table in that there * is not an array of structures like other tables. Use of this * table requires special handling in do_sql. **************************************************************/ /* Define the table columns */ COLDEF rta_tablesCols[] = { { "rta_tables", /* table name */ "name", /* column name */ RTA_PSTR, /* type of data */ MXTBLNAME, /* #bytes in col data */ offsetof(TBLDEF, name), /* offset 2 col strt */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "The name of the table. This must be unique in the system. " " Table names can be at most MXTBLNAME characters in length." " See rta.h for details. Note that some table names are " "reserved for internal use."}, { "rta_tables", /* table name */ "address", /* column name */ RTA_PTR, /* type of data */ sizeof(void *), /* #bytes in col data */ offsetof(TBLDEF, address), /* offset 2 col strt */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "The start address of the array of structs that makes up " "the table."}, { "rta_tables", /* table name */ "rowlen", /* column name */ RTA_INT, /* type of data */ sizeof(int), /* #bytes in col data */ offsetof(TBLDEF, rowlen), /* offset 2 col strt */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "The length of each struct in the array of structs that " "makes up the table."}, { "rta_tables", /* table name */ "nrows", /* column name */ RTA_INT, /* type of data */ sizeof(int), /* #bytes in col data */ offsetof(TBLDEF, nrows), /* offset 2 col strt */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "The number of rows in the table."}, { "rta_tables", /* table name */ "iterator", /* column name */ RTA_PTR, /* type of data */ sizeof(void *), /* #bytes in col data */ offsetof(TBLDEF, iterator), /* offset 2 col strt */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "The iterator is a function that, given a pointer to a " "row, returns a pointer to the next row. When passed " "a NULL as input, the function returns a pointer to the " "first row of the table. The function return a NULL when " "asked for the row after the last row. The function is " "useful to walk through the rows of a linked list."}, { "rta_tables", /* table name */ "it_info", /* column name */ RTA_PTR, /* type of data */ sizeof(void *), /* #bytes in col data */ offsetof(TBLDEF, it_info), /* offset 2 col strt */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "This is a pointer to any kind of information that the " "caller wants returned with each iterator call. For " "example, you may wish to have one iterator function " "for all of your linked lists. You could pass in " "a unique identifier for each table so the function " "can handle each one as appropriate. "}, { "rta_tables", /* table name */ "cols", /* column name */ RTA_PTR, /* type of data */ sizeof(void *), /* #bytes in col data */ offsetof(TBLDEF, cols), /* offset 2 col strt */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "A pointer to an array of COLDEF structures. There is one " "COLDEF for each column in the table."}, { "rta_tables", /* table name */ "ncol", /* column name */ RTA_INT, /* type of data */ sizeof(int), /* #bytes in col data */ offsetof(TBLDEF, ncol), /* offset 2 col strt */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "The number of columns in the table."}, { "rta_tables", /* table name */ "savefile", /* column name */ RTA_PSTR, /* type of data */ MXFILENAME, /* #bytes in col data */ offsetof(TBLDEF, savefile), /* offset 2 col strt */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "The name of the file with the non-volatile contents of " "the table. This file is read when the table is " "initialized and is written any time a column with the " "non-volatile flag set is modified."}, { "rta_tables", /* table name */ "help", /* column name */ RTA_PSTR, /* type of data */ MXHELPSTR, /* #bytes in col data */ offsetof(TBLDEF, help), /* offset 2 col strt */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "A description of the table."}, }; /* Define the table */ TBLDEF rta_tablesTable = { "rta_tables", /* table name */ (void *) 0, /* address of table */ sizeof(TBLDEF), /* length of each row */ 0, /* It's a pseudo table */ get_next_sysrow, /* iterator function */ (void *) RTA_TABLES, /* iterator callback data */ rta_tablesCols, /* Column definitions */ sizeof(rta_tablesCols) / sizeof(COLDEF), /* # columns */ "", /* save file name */ "The table of all tables in the system. This is a pseudo " "table and not an array of structures like other tables." }; /*************************************************************** * get_next_sysrow(): - Routine to the get the next row pointer * given the current row pointer or row number. * * Input: Pointer to current row * Callback data (RTA_TABLES or RTA_COLUMNS) * Desired row number (ie current +1) * Output: Pointer to next row or NULL if at end of list * Effects: None **************************************************************/ void * get_next_sysrow(void *pui, void *it_info, int rowid) { extern TBLDEF *Tbl[]; extern COLDEF *Col[]; extern int Ntbl; extern int Ncol; /* The tables for tables and columns contain pointers to the actual TBLDEF and COLDEF structures. This saves memory and makes it easy for a program to change parts of the table or column definition when needed. So this routine just return the Tbl or Col value. */ if (((int) it_info == RTA_TABLES) && (rowid < Ntbl)) { return ((void *) Tbl[rowid]); } else if (((int) it_info == RTA_COLUMNS) && (rowid < Ncol)) { return ((void *) Col[rowid]); } /* Must be at end of list */ return ((void *) NULL); } /*************************************************************** * The rta_dbg table controls which errors generate * debug log messages, the priority, and the facility of the * syslog() messages sent. The rta package generates no user * level log * messages, only debug messages. All of the fields * in this table are volatile. You will need to set the values * in your main program to make them seem persistent. * (Try something like * "SQL_string("UPDATE rta_dbgconfig SET dbg ....").) * A callback attached to dbg_facility causes a close/reopen of * syslog(). **************************************************************/ /* Allocate and initialize the table */ struct RtaDbg rtadbg = { 1, /* log system errors */ 1, /* log rta errors */ 1, /* log SQL errors */ 0, /* no log of SQL cmds */ 1, /* log to syslog() only */ LOG_ERR, /* see sys/syslog.h */ LOG_USER, /* see sys/syslog.h */ "rta" /* see 'man openlog' */ }; /* Define the table columns */ COLDEF rta_dbgCols[] = { { "rta_dbg", /* table name */ "syserr", /* column name */ RTA_INT, /* type of data */ sizeof(int), /* #bytes in col data */ offsetof(struct RtaDbg, syserr), /* offset 2 col strt */ 0, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "A non-zero value causes a call to syslog() for all system " "errors such as failed malloc() or save file read failures."}, { "rta_dbg", /* table name */ "rtaerr", /* column name */ RTA_INT, /* type of data */ sizeof(int), /* #bytes in col data */ offsetof(struct RtaDbg, rtaerr), /* offset 2 col strt */ 0, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "A non-zero value causes a call to syslog() for all errors " "internal to the rta package."}, { "rta_dbg", /* table name */ "sqlerr", /* column name */ RTA_INT, /* type of data */ sizeof(int), /* #bytes in col data */ offsetof(struct RtaDbg, sqlerr), /* offset 2 col strt */ 0, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "A non-zero value causes a call to syslog() for all SQL " "errors. Such errors usually indicate a programming error " "in one of the user interface programs."}, { "rta_dbg", /* table name */ "trace", /* column name */ RTA_INT, /* type of data */ sizeof(int), /* #bytes in col data */ offsetof(struct RtaDbg, trace), /* offset 2 col strt */ 0, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "A non-zero value causes all SQL commands to be logged. " "If the command is UPDATE, the number of rows affected is " "also logged."}, { "rta_dbg", /* table name */ "target", /* column name */ RTA_INT, /* type of data */ sizeof(int), /* #bytes in col data */ offsetof(struct RtaDbg, target), /* offset 2 col strt */ 0, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ restart_syslog, /* called after write */ "Sets destination of log messages. Zero turns off all " "logging of errors. One sends log messages to syslog()." " Two sends log messages to stderr. Three sends error " "messages to both syslog() and to stderr. Default is one."}, { "rta_dbg", /* table name */ "priority", /* column name */ RTA_INT, /* type of data */ sizeof(int), /* #bytes in col data */ offsetof(struct RtaDbg, priority), /* offset 2 col strt */ 0, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "The syslog() priority. Please see .../sys/syslog.h for " "the possible values. Default is LOG_ERR."}, { "rta_dbg", /* table name */ "facility", /* column name */ RTA_INT, /* type of data */ sizeof(int), /* #bytes in col data */ offsetof(struct RtaDbg, facility), /* offset 2 col strt */ 0, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "The syslog() facility. Please see .../sys/syslog.h for " "the possible values. Default is LOG_USER."}, { "rta_dbg", /* table name */ "ident", /* column name */ RTA_STR, /* type of data */ MXDBGIDENT, /* #bytes in col data */ offsetof(struct RtaDbg, ident), /* offset 2 col strt */ 0, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "The syslog() 'ident'. Please see 'man openlog' for " "details. Default is 'rta'. An update of the target " "field is required for this to take effect."}, }; /*************************************************************** * restart_syslog(): - Routine to restart or reconfigure the * logging facility. Syslog is always closed and (if enabled) * reopened with the priority and facility specified in the * rtadbg structure. * * Input: Name of the table * Name of the column * Text of the SQL command itself * Pointer to row of data * Pointer to copy of old row * Index of row used (zero indexed) * Output: Success (a zero) * Effects: No side effects. **************************************************************/ int restart_syslog(char *tblname, char *colname, char *sqlcmd, void *prow, void *poldrow, int rowid) { extern struct RtaDbg rtadbg; closelog(); if (rtadbg.target == 1 || rtadbg.target == 3) { openlog(rtadbg.ident, LOG_ODELAY | LOG_PID, rtadbg.facility); } return(0); } /* Define the table */ TBLDEF rta_dbgTable = { "rta_dbg", /* table name */ (void *) &rtadbg, /* address of table */ sizeof(struct RtaDbg), /* length of each row */ 1, /* # rows in table */ (void *) NULL, /* iterator function */ (void *) NULL, /* iterator callback data */ rta_dbgCols, /* Column definitions */ sizeof(rta_dbgCols) / sizeof(COLDEF), /* # columns */ "", /* save file name */ "Configure of debug logging. A callback on the 'target' " "field closes and reopens syslog(). None of the values " "in this table are saved to disk. If you want non-default " "values you need to change the rta source or do an " "SQL_string() to set the values when you initialize your " "program." }; /*************************************************************** * The rta_stats table contains usage and error statistics * which might be of interest to developers. All fields are * of type long, are read-only, and are set to zero by * rta_init(). **************************************************************/ /* Allocate and initialize the table */ struct RtaStat rtastat = { (llong) 0, /* count of failed OS calls. */ (llong) 0, /* count of internal rta failures. */ (llong) 0, /* count of SQL failures. */ (llong) 0, /* count of authorizations. */ (llong) 0, /* count of UPDATE requests */ (llong) 0, /* count of SELECT requests */ }; /* Define the table columns */ COLDEF rta_statCols[] = { { "rta_stat", /* table name */ "nsyserr", /* column name */ RTA_LONG, /* type of data */ sizeof(llong), /* #bytes in col data */ offsetof(struct RtaStat, nsyserr), /* offset 2 col strt */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "Count of failed OS calls."}, { "rta_stat", /* table name */ "nrtaerr", /* column name */ RTA_LONG, /* type of data */ sizeof(llong), /* #bytes in col data */ offsetof(struct RtaStat, nrtaerr), /* offset 2 col strt */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "Count of internal rta failures."}, { "rta_stat", /* table name */ "nsqlerr", /* column name */ RTA_LONG, /* type of data */ sizeof(llong), /* #bytes in col data */ offsetof(struct RtaStat, nsqlerr), /* offset 2 col strt */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "Count of SQL failures."}, { "rta_stat", /* table name */ "nauth", /* column name */ RTA_LONG, /* type of data */ sizeof(llong), /* #bytes in col data */ offsetof(struct RtaStat, nauth), /* offset 2 col strt */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "Count of DB authorizations. This is a good estimate " "to the total number of connections."}, { "rta_stat", /* table name */ "nselect", /* column name */ RTA_LONG, /* type of data */ sizeof(llong), /* #bytes in col data */ offsetof(struct RtaStat, nselect), /* offset 2 col strt */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "Count of SELECT commands."}, { "rta_stat", /* table name */ "nupdate", /* column name */ RTA_LONG, /* type of data */ sizeof(llong), /* #bytes in col data */ offsetof(struct RtaStat, nupdate), /* offset 2 col strt */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "Count of UPDATE commands."}, }; /* Define the table */ TBLDEF rta_statTable = { "rta_stat", /* table name */ (void *) &rtastat, /* address of table */ sizeof(struct RtaStat), /* length of each row */ 1, /* # rows in table */ (void *) NULL, /* iterator function */ (void *) NULL, /* iterator callback data */ rta_statCols, /* Column definitions */ sizeof(rta_statCols) / sizeof(COLDEF), /* # columns */ "", /* save file name */ "Usage and error counts for the rta package." }; Node-path: src/token.l Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 5517 Text-content-md5: 0639b139f338f658d7ae17f2f31c7cc1 Content-length: 5527 PROPS-END /*************************************************************** * Run Time Access * Copyright (C) 2003-2006 Robert W Smith (bsmith@linuxtoys.org) * * This program is distributed under the terms of the GNU LGPL. * See the file COPYING file. **************************************************************/ /*************************************************************** * token.l -- Lex tokenizer for SQL commands. **************************************************************/ %option noyywrap %option never-interactive %{ #define __USE_GNU #include #include "do_sql.h" #include "parse.tab.h" void dosql_init(); extern int yylval; extern int yydebug; extern struct Sql_Cmd cmd; extern char *parsestr[]; %} %% [Aa][Nn][Dd] { return(AND); } [Ff][Rr][Oo][Mm] { return(FROM); } [Ll][Ii][Mm][Ii][Tt] { return(LIMIT); } [Oo][Ff][Ff][Ss][Ee][Tt] { return(OFFSET); } [Ss][Ee][Ll][Ee][Cc][Tt] { return(SELECT); } [Ss][Ee][Tt] { return(SET); } [Uu][Pp][Dd][Aa][Tt][Ee] { return(UPDATE); } [Ww][Hh][Ee][Rr][Ee] { return(WHERE); } \"[A-Za-z][_A-Za-z0-9 \t]*\" | \'[A-Za-z][_A-Za-z0-9 \t]*\' { int i; for (i=0; i { return(GT); } \< { return(LT); } \>= { return(GE); } \<= { return(LE); } \, { return((int)','); } \( { return((int)'('); } \) { return((int)')'); } \"[A-Za-z0-9 \t!@#$%^&*()_+-={}|;:<>?~`\[\]'\\]*\" | \'[A-Za-z0-9 \t!@#$%^&*()_+-={}|;:<>?~`\[\]"\\]*\' { int i; for (i=0; i> { return(TERMINATOR); /* done with this buffer of SQL */ } . { return(GARBAGE); /* Causes a syntax error */} %% void SQL_string(char *s, int incnt, char *out, int *nout) { extern int yyparse(); YY_BUFFER_STATE x; int i; /* Sanity checks */ if (!s || !out || !nout) { return; } dosql_init(); cmd.out = out; cmd.nout = nout; cmd.sqlcmd = s; /* We need to store the start addr of the buffer in case we * we need to send an error message after we've started * sending a reply. */ cmd.errout = out; cmd.nerrout = *nout; cmd.nlineout = 0; x = yy_scan_bytes(s, incnt); while(yyparse() == 0) { /* At this point we have parsed the command. */ /* If no errors were detected, we can continue processing */ if (!cmd.err) { /* everything is set. do the command */ do_sql(&out[cmd.nerrout - *nout], nout); } else { yy_delete_buffer(x); return; } dosql_init(); /* free memory and re-init the cmd structure */ } /* We are done processing the command and have assembled response */ /* Tell the other end that we are ready for a new command. */ i = cmd.nerrout - *nout; out[i++] = 'Z'; /* Ready */ out[i++] = 0; /* byte 4 of length */ out[i++] = 0; /* byte 3 of length */ out[i++] = 0; /* byte 2 of length */ out[i++] = 5; /* byte 1 of length */ out[i++] = 'I'; /* status of result */ *nout -= 6; yy_delete_buffer(x); return; } #ifdef xxxx extern int yydebug; yydebug = 1; printf("N-out = %d\n", *cmd.nout); printf("Scanning ---%s---\n", s); printf("Limit = %d\n", cmd.limit); printf("Offset = %d\n", cmd.offset); printf("Table = %s\n", cmd.tbl); for (i=0; i RTA Table Editor

RTA Table Editor

App NamePort Number
myapp8888
Node-path: table_editor/rta_edit.php Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 3829 Text-content-md5: de3557579e16011cfe600d0daf6cc068 Content-length: 3839 PROPS-END Edit Row

Edit $tbl, row $row

\n"); // Suppress Postgres error messages error_reporting(error_reporting() & 0xFFFD); // connect to the database $c1 = pg_connect("localhost", "$port", "bsmith"); if ($c1 == "") { printf("$s%s%s", "Unable to connect to application.
", "Please verify that the application is running and ", "listening on port $port.
"); exit(); } // Give URL for form processing print("\n"); print("\n"); print("\n"); print("\n"); // print name, help, value of each column print("
\n"); // execute query $command = "SELECT name, flags, help, length, type FROM rta_columns WHERE table='$tbl'"; $r1 = pg_exec($c1, $command); if ($r1 == "") { print("

SQL Command failed!

"); print("

Command: $command

\n"); exit(); } print("
\n"); print("\n"); for($col = 0; $col < pg_NumRows($r1); $col++) { $colname = pg_result($r1, $col, 0); $colflags = pg_result($r1, $col, 1); $colhelp = pg_result($r1, $col, 2); $collength = pg_result($r1, $col, 3); $coltype = pg_result($r1, $col, 4); if (($coltype != 0) && // "0" is the type for strings, See rta.h ($coltype != 4)) // "4" is the type for pointer to string. $collength = 20; // Get column value from table $command = "SELECT \"$colname\" FROM \"$tbl\" LIMIT 1 OFFSET $row"; $r2 = pg_exec($c1, $command); if ($r2 == "") { print("

SQL Command failed!

"); print("

Command: $command

\n"); exit(); } $colvalue = pg_result($r2, 0, 0); pg_freeresult($r2); // Display the column print("
"); if ($colflags & 2) // "2" indicates a read-only field. See rta.h { print("\n"); } else { print("\n"); } } print("
ColumnValue
$colname
$colhelp
$colvalue
"); print("
\n"); print("

\n"); // free the result and close the connection pg_freeresult($r1); pg_close($c1); ?> Node-path: table_editor/rta_tables.php Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 2292 Text-content-md5: cead02769115731f5cb768af09314b13 Content-length: 2302 PROPS-END RTA Table Editor

RTA Table Editor

", "Please verify that the application is running and ", "listening on port $port.
"); exit(); } // Headings print("\n"); print("\n"); // execute query $command = "SELECT name, help, nrows FROM rta_tables"; $result = pg_exec($conn, $command); if ($result == "") { print("

SQL Command failed!

"); print("

Command: $command

\n"); exit(); } for($row = 0; $row < pg_NumRows($result); $row++) { $tblname = pg_result($result, $row, 0); $tblhelp = pg_result($result, $row, 1); $tblrows = pg_result($result, $row, 2); print("
\n\n"); print("\n"); } print("
Table NameDescription
$tblname$tblhelp
\n"); // free the result and close the connection pg_freeresult($result); pg_close($conn); ?> Node-path: table_editor/rta_update.php Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 2491 Text-content-md5: a90271187fca7ae16e1a33b5a28cc331 Content-length: 2501 PROPS-END Edit Row

Update $tbl, row $row


\n"); // Suppress Postgres error messages error_reporting(error_reporting() & 0xFFFD); // connect to the database $c1 = pg_connect("localhost", "$port", "anyuser"); if ($c1 == "") { printf("$s%s", "Unable to connect to application.
", "Please verify that the application is running and ", "listening on port $port.
"); exit(); } // Build SQL UPDATE command. $command = "UPDATE $tbl SET "; $count = count($_POST); for ($index=3; $index < $count; $index++) { // use "htmlentities()" to protect from malicious HTML // $value=htmlentities(next($_POST)); // $key = htmlentities(key($_POST)); $value=next($_POST); $key = key($_POST); if ($index > 3) $command = "$command, \"$key\" = \"$value\" "; else $command = "$command \"$key\" = \"$value\" "; } $command = "$command LIMIT 1 OFFSET $row"; // execute query $r1 = pg_exec($c1, $command); if ($r1 == "") { print("

Update failed!

"); print("

Please verify input values.

\n"); print("

Command: $command

\n"); exit(); } // Update succeeded. Say so. print("

Update succeeded."); print("

\n

Command: $command

\n"); // free the result and close the connection pg_freeresult($r1); pg_close($c1); ?> Node-path: table_editor/rta_view.php Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 3581 Text-content-md5: c73242d845afc37310aaabdab8cd9a7c Content-length: 3591 PROPS-END Table View
$tbl
\n"); // Suppress Postgres error messages error_reporting(error_reporting() & 0xFFFD); // connect to the database $connection = pg_connect("localhost", "$port", "bsmith"); if ($connection == "") { printf("$s%s%s", "Unable to connect to application.
", "Please verify that the application is running and ", "listening on port $port.
"); exit(); } // print each row in a table print("
\n"); // Get and print column names $command = "SELECT name, flags FROM rta_columns WHERE table = $tbl"; $result = pg_exec($connection, $command); if ($result == "") { print("

SQL Command failed!

"); print("

Command: $command

\n"); exit(); } // A flag to say there's at least one editable column. $readonly = 2; // "2" indicates a read-only column. See rta.h print("
\n"); for($row = 0; $row < pg_NumRows($result); $row++) { $colname = pg_result($result, $row, 0); $colflags = pg_result($result, $row, 1); print(""); $readonly = $readonly & $colflags; } if ($readonly == 0) print("\n"); print("\n"); pg_freeresult($result); // execute query $command = "SELECT * FROM $tbl LIMIT 20 OFFSET $offset"; $result = pg_exec($connection, $command); if ($result == "") { print("

SQL Command failed!

"); print("

Command: $command

\n"); exit(); } for($row = 0; $row < pg_NumRows($result); $row++) { print("
\n"); for($field = 0; $field < pg_numfields($result); $field++) { print("\n"); } // Add link to edit the row if editable. if ($readonly == 0) { $rowindex = $offset + $row; print(""); } print("\n"); } print("
$colname 
"); print(pg_result($result, $row, $field)); print("(edit)
\n"); // Add link to next set of rows if needed if ($offset + 20 < $nrows) { $offset = $offset + 20; $nextcount = $nrows - $offset; if ($nrows - $offset > 20) $nextcount = 20; print("

Next $nextcount rows >

\n"); } // free the result and close the connection pg_freeresult($result); pg_close($connection); ?> Node-path: test Node-kind: dir Node-action: add Prop-content-length: 10 Content-length: 10 PROPS-END Node-path: test/Makefile Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 1485 Text-content-md5: 9fbdfcffab4893c8d732482841298b68 Content-length: 1495 PROPS-END ################################################################ # Run Time Access # Copyright (C) 2003-2006 Robert W Smith (bsmith@linuxtoys.org) # # This program is distributed under the terms of the GNU LGPL. # See the file COPYING file. ################################################################ ################################################################ # Makefile -- to make the sample application for rta. # Add or remove debug stuff to meet your needs. # The "standard" target runs the source through indent to # give the source a standardized look. ################################################################ # We use the default make rules for .c and .l CC = gcc OPT = -fPIC -c -I. DEBUG = -g -DDEBUG -DYYDEBUG -ggdb -Wall CFLAGS = $(OPT) $(DEBUG) APPOBJS = appmain.o apptables.o default: app rta_client app: $(APPOBJS) $(CC) $(APPOBJS) -o $@ -L../src -lrtadb app-static: $(APPOBJS) $(CC) $(APPOBJS) -o $@ ../src/librtadb.a rta_client: rta_client.o $(CC) rta_client.c -o $@ -lpq appmain.o: appmain.c app.h $(CC) $(CFLAGS) $< -o $@ apptables.o: apptables.c app.h $(CC) $(CFLAGS) $< -o $@ standard: clean for i in *.h *.c ; \ do \ indent -bad -bap -nbbb -bli0 -cdw -cli2 -cbi0 -saf -nut \ -sai -saw -di9 -nbc -bfda -bls -lp -ci2 -i2 -nbbo -l72 \ -fca -nbfda -nbfde -nlp -bbo -sob -cd24 -ts0 -npcs -nut \ -T TBLDEF -T COLDEF -T PRXPORT $$i ; \ rm $$i~ ; \ done clean: rm -rf *.o app app-static rta_client Node-path: test/app.h Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 2266 Text-content-md5: 9c7eedc54fa644c141b589b560ef5c36 Content-length: 2276 PROPS-END /*************************************************************** * Run Time Access * Copyright (C) 2003-2006 Robert W Smith (bsmith@linuxtoys.org) * * This program is distributed under the terms of the GNU LGPL. * See the file COPYING file. **************************************************************/ /*************************************************************** * app.h -- * * Overview: * This application demonstrates the use of the rta package * using select() for multiplexing. **************************************************************/ #include "../src/rta.h" /* Maximum number of UI/Posgres connections */ #define MX_UI (20) /* Max size of a Postgres packet from/to the UI's */ #define MXCMD (1000) #define MXRSP (50000) /* Note length and number of rows in the "mydata" table. */ #define NOTE_LEN 20 #define ROW_COUNT 20 /* -structure definitions */ /*************************************************************** * the structure for the sample application table. This is * where you would put your real application tables. **************************************************************/ struct MyData { int myint; float myfloat; char notes[NOTE_LEN]; char seton[NOTE_LEN]; }; /*************************************************************** * the information kept for TCP connections to UI programs **************************************************************/ typedef struct ui { struct ui *prevconn; /* Points to previous conn in linked list */ struct ui *nextconn; /* Points to next conn in linked list */ int fd; /* FD of TCP conn (=-1 if not in use) */ int cmdindx; /* Index of next location in cmd buffer */ char cmd[MXCMD]; /* SQL command from UI program */ int rspfree; /* Number of free bytes in rsp buffer */ char rsp[MXRSP]; /* SQL response to the UI program */ int o_port; /* Other-end TCP port number */ int o_ip; /* Other-end IP address */ llong nbytin; /* number of bytes read in */ llong nbytout; /* number of bytes sent out */ int ctm; /* connect time (==time();) */ int cdur; /* duration time (== now()-ctm;) */ } UI; Node-path: test/appmain.c Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 15341 Text-content-md5: f461582323ec38bf95dd503e9c0f30b6 Content-length: 15351 PROPS-END /*************************************************************** * Run Time Access * Copyright (C) 2003-2006 Robert W Smith (bsmith@linuxtoys.org) * * This program is distributed under the terms of the GNU LGPL. * See the file COPYING file. **************************************************************/ /*************************************************************** * appmain.c -- * * Overview: * This is a sample application which illustrates the use of * the rta package. It uses select() for I/O multiplexing and * offers the tables of UI connections (uiconns) to the user * interface as a DB table (UIConns). **************************************************************/ /* Layout of this file: * -includes, defines, and forward references * -allocate storage for tables and variables * -main() routine * -other routines in alphabetical order * -includes, defines, and forward references */ #include #include #include #include #include #include #include #include #include #include #include /* for time() function */ #include #include "app.h" #define DB_PORT 8888 void accept_ui_session(int srvfd); int compute_cdur(char *tbl, char *col, char *sql, void *pr, int rowid); void handle_ui_output(UI *pui); void handle_ui_request(UI *pui); void init_ui(); int listen_on_port(int port); int reverse_str(char *tbl, char *col, char *sql, void *pr, int rowid, void *por); void *get_next_conn(void *prow, void *it_data, int rowid); extern TBLDEF UITables[]; extern int nuitables; /* -allocate static, global storage for tables and variables */ UI *ConnHead; /* head of linked list of UI conns */ int nui = 0; /* number of open UI connections */ struct MyData mydata[ROW_COUNT]; /*************************************************************** * How this program works: * - Allocate and initialize system variables (as DB tables) * - Open socket to listen for DB config commands * - select() loop **************************************************************/ int main() { fd_set rfds; /* read bit masks for select statement */ fd_set wfds; /* write bit masks for select statement */ int mxfd; /* Maximum FD for the select statement */ int newui_fd = -1; /* FD to TCP socket accept UI conns */ int i; /* generic loop counter */ UI *pui; /* pointer to a UI struct */ UI *nextpui; /* points to next UI in list */ /* Init */ for (i = 0; i < nuitables; i++) { rta_add_table(&UITables[i]); } ConnHead = (UI *) NULL; while (1) { /* Build the fd_set for the select call. This includes the listen port for new UI connections and any existing UI connections. We also look for the ability to write to the clients if data is queued. */ FD_ZERO(&rfds); FD_ZERO(&wfds); mxfd = 0; /* open UI/DB/manager listener if needed */ if (newui_fd < 0) { newui_fd = listen_on_port(DB_PORT); } FD_SET(newui_fd, &rfds); mxfd = (newui_fd > mxfd) ? newui_fd : mxfd; /* for each UI conn .... */ pui = ConnHead; while (pui) { if (pui->rspfree < MXRSP) /* Data to send? */ { FD_SET(pui->fd, &wfds); mxfd = (pui->fd > mxfd) ? pui->fd : mxfd; } else { FD_SET(pui->fd, &rfds); mxfd = (pui->fd > mxfd) ? pui->fd : mxfd; } pui = pui->nextconn; } /* Wait for some something to do */ (void) select(mxfd + 1, &rfds, &wfds, (fd_set *) 0, (struct timeval *) 0); /* ....after the select call. We have activity. Search through the open fd's to find what to do. */ /* Handle new UI/DB/manager connection requests */ if ((newui_fd >= 0) && (FD_ISSET(newui_fd, &rfds))) { accept_ui_session(newui_fd); } /* process request from or data to one of the UI programs */ pui = ConnHead; while (pui) { /* Get next UI now since pui struct may be freed in handle_ui.. */ nextpui = pui->nextconn; if (FD_ISSET(pui->fd, &rfds)) { handle_ui_request(pui); } else if (FD_ISSET(pui->fd, &wfds)) { handle_ui_output(pui); } pui = nextpui; } } } /*************************************************************** * accept_ui_session(): - Accept a new UI/DB/manager session. * This routine is called when a user interface program such * as Apache (for the web interface), the SNMP manager, or one * of the console interface programs tries to connect to the * data base interface socket to do DB like get's and set's. * The connection is actually established by the PostgreSQL * library attached to the UI program by an XXXXXX call. * * Input: The file descriptor of the DB server socket * Output: none * Effects: manager connection table (ui) ***************************************************************/ void accept_ui_session(int srvfd) { int newuifd; /* New UI FD */ u_int adrlen; /* length of an inet socket address */ struct sockaddr_in cliskt; /* socket to the UI/DB client */ int flags; /* helps set non-blocking IO */ UI *pnew; /* pointer to the new UI struct */ UI *pui; /* pointer to a UI struct */ /* Accept the connection */ adrlen = sizeof(struct sockaddr_in); newuifd = accept(srvfd, (struct sockaddr *) &cliskt, &adrlen); if (newuifd < 0) { syslog(LOG_ERR, "Manager accept() error"); return; } /* We've accepted the connection. Now get a UI structure. Are we at our limit? If so, drop the oldest conn. */ if (nui >= MX_UI) { syslog(LOG_WARNING, "no manager connections"); /* oldest conn is one at head of linked list. Close it and promote next oldest to the top of the linked list. */ close(ConnHead->fd); pui = ConnHead->nextconn; free(ConnHead); nui--; ConnHead = pui; ConnHead->prevconn = (UI *) NULL; } pnew = malloc(sizeof (UI)); if (pnew == (UI *) NULL) { /* Unable to allocate memory for new connection. Log it, then drop new connection. Try to go on.... */ syslog(LOG_ERR, "Unable to allocate memory"); close(newuifd); return; } nui++; /* increment number of UI structs alloc'ed */ /* OK, we've got the UI struct, now add it to end of list */ if (ConnHead == (UI *) NULL) { ConnHead = pnew; pnew->prevconn = (UI *) NULL; pnew->nextconn = (UI *) NULL; } else { pui = ConnHead; while (pui->nextconn != (UI *) NULL) pui = pui->nextconn; pui->nextconn = pnew; pnew->prevconn = pui; pnew->nextconn = (UI *) NULL; } /* UI struct is now at end of list. Fill it in. */ pnew->fd = newuifd; flags = fcntl(pnew->fd, F_GETFL, 0); flags |= O_NONBLOCK; (void) fcntl(pnew->fd, F_SETFL, flags); pnew->o_ip = (int) cliskt.sin_addr.s_addr; pnew->o_port = (int) ntohs(cliskt.sin_port); pnew->cmdindx = 0; pnew->rspfree = MXRSP; pnew->ctm = (int) time((time_t *) 0); pnew->nbytin = 0; pnew->nbytout = 0; } /*************************************************************** * compute_cdur() - a read callback to compute the number of * seconds the TCP connection has been up. * * Input: char *tbl -- the table read (UIConns) * char *col -- the column read (cdur) * char *sql -- actual SQL of the command * void *pr -- points to row affected * int rowid -- row number of row read * Output: 0 (success) * Effects: Computes the difference between the current * value of time() and the value stored in the * field 'ctm'. The result is placed in 'cdur'. ***************************************************************/ int compute_cdur(char *tbl, char *col, char *sql, void *pr, int rowid) { if(pr) ((UI *)pr)->cdur = ((int) time((time_t *) 0)) - ((UI *)pr)->ctm; return(0); } /*************************************************************** * handle_ui_request(): - This routine is called to read data * from the TCP connection to the UI programs such as the web * UI and consoles. The protocol used is that of Postgres and * the data is an encoded SQL request to select or update a * system variable. Note that the use of callbacks on reading * or writing means a lot of the operation of the program * starts from this execution path. The input is an index into * the ui table for the manager with data ready. * * Input: pointer to UI struct with data to read * Output: none * Effects: many, many side effects via table callbacks ***************************************************************/ void handle_ui_request(UI *pui) { int ret; /* a return value */ int dbstat; /* a return value */ int t; /* a temp int */ /* We read data from the connection into the buffer in the ui struct. Once we've read all of the data we can, we call the DB routine to parse out the SQL command and to execute it. */ ret = read(pui->fd, &(pui->cmd[pui->cmdindx]), (MXCMD - pui->cmdindx)); /* shutdown manager conn on error or on zero bytes read */ if (ret <= 0) { /* log this since a normal close is with an 'X' command from the client program? */ close(pui->fd); /* Free the UI struct */ if (pui->prevconn) (pui->prevconn)->nextconn = pui->nextconn; else ConnHead = pui->nextconn; if (pui->nextconn) (pui->nextconn)->prevconn = pui->prevconn; free(pui); nui--; return; } pui->cmdindx += ret; pui->nbytin += ret; /* The commands are in the buffer. Call the DB to parse and execute them */ do { t = pui->cmdindx; /* packet in length */ dbstat = dbcommand(pui->cmd, /* packet in */ &(pui->cmdindx), /* packet in length */ &(pui->rsp[MXRSP - pui->rspfree]), /* ptr to out buf */ &(pui->rspfree)); /* N bytes at out */ t -= pui->cmdindx; /* t = # bytes consumed */ /* move any trailing SQL cmd text up in the buffer */ (void) memmove(pui->cmd, &(pui->cmd[t]), t); } while (dbstat == RTA_SUCCESS); /* the command is done (including side effects). Send any reply back to the UI. You may want to check for RTA_CLOSE here. */ handle_ui_output(pui); } /*************************************************************** * handle_ui_output() - This routine is called to write data * to the TCP connection to the UI programs. It is useful for * slow clients which can not accept the output in one big gulp. * * Input: pointer to UI structure ready for write * Output: none * Effects: none ***************************************************************/ void handle_ui_output(UI *pui) { int ret; /* write() return value */ if (pui->rspfree < MXRSP) { ret = write(pui->fd, pui->rsp, (MXRSP - pui->rspfree)); if (ret < 0) { /* log a failure to talk to a DB/UI connection */ fprintf(stderr, "error #%d on ui write to port #%d on IP=%d\n", errno, pui->o_port, pui->o_ip); close(pui->fd); /* Free the UI struct */ if (pui->prevconn) (pui->prevconn)->nextconn = pui->nextconn; else ConnHead = pui->nextconn; if (pui->nextconn) (pui->nextconn)->prevconn = pui->prevconn; free(pui); nui--; return; } else if (ret == (MXRSP - pui->rspfree)) { pui->rspfree = MXRSP; pui->nbytout += ret; } else { /* we had a partial write. Adjust the buffer */ (void) memmove(pui->rsp, &(pui->rsp[ret]), (MXRSP - pui->rspfree - ret)); pui->rspfree += ret; pui->nbytout += ret; /* # bytes sent on conn */ } } } /*************************************************************** * listen_on_port(int port): - Open a socket to listen for * incoming TCP connections on the port given. Return the file * descriptor if OK, and -1 on any error. The calling routine * can handle any error condition. * * Input: The interger value of the port number to bind to * Output: The file descriptor of the socket * Effects: none ***************************************************************/ int listen_on_port(int port) { int srvfd; /* FD for our listen server socket */ struct sockaddr_in srvskt; int adrlen; int flags; adrlen = sizeof(struct sockaddr_in); (void) memset((void *) &srvskt, 0, (size_t) adrlen); srvskt.sin_family = AF_INET; srvskt.sin_addr.s_addr = INADDR_ANY; srvskt.sin_port = htons(port); if ((srvfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { fprintf(stderr, "Unable to get socket for port %d.", port); exit(1); } flags = fcntl(srvfd, F_GETFL, 0); flags |= O_NONBLOCK; (void) fcntl(srvfd, F_SETFL, flags); if (bind(srvfd, (struct sockaddr *) &srvskt, adrlen) < 0) { fprintf(stderr, "Unable to bind to port %d\n", port); exit(1); } if (listen(srvfd, 1) < 0) { fprintf(stderr, "Unable to listen on port %d\n", port); exit(1); } return (srvfd); } /*************************************************************** * reverse_str(): - a write callback to replace '<' and '>' with * '.', and to store the reversed string of notes into seton. * * Input: char *tbl -- the table modified * char *col -- the column modified * char *sql -- actual SQL of the command * void *pr -- points to row * void *por -- points to copy of row before update * int rowid -- row number of row modified * Output: 0 (success) * Effects: Puts the reverse of 'notes' into 'seton' ***************************************************************/ int reverse_str(char *tbl, char *col, char *sql, void *pr, int rowid, void *por) { int i, j; /* loop counters */ i = strlen(mydata[rowid].notes) - 1; /* -1 to ignore NULL */ for (j = 0; i >= 0; i--, j++) { if (mydata[rowid].notes[i] == '<' || mydata[rowid].notes[i] == '>') mydata[rowid].notes[i] = '.'; mydata[rowid].seton[j] = mydata[rowid].notes[i]; } mydata[rowid].seton[j] = (char) 0; if (mydata[rowid].myint == 999) return(1); return(0); } /*************************************************************** * get_next_conn(): - an 'iterator' on the linked list of TCP * connections. * * Input: void *prow -- pointer to current row * void *it_data -- callback data. Unused. * int rowid -- the row number. Unused. * Output: pointer to next row. NULL on last row * Effects: No side effects ***************************************************************/ void * get_next_conn(void *prow, void *it_data, int rowid) { if (prow == (void *) NULL) return((void *) ConnHead); return((void *) ((UI *)prow)->nextconn); } Node-path: test/apptables.c Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 12003 Text-content-md5: 7a28600d92a56f30d5c5c236f5066e94 Content-length: 12013 PROPS-END /*************************************************************** * Run Time Access * Copyright (C) 2003-2006 Robert W Smith (bsmith@linuxtoys.org) * * This program is distributed under the terms of the GNU LGPL. * See the file COPYING file. **************************************************************/ /*************************************************************** * apptables.c -- * * Overview: * The "rta" package provides a Postgres-like API into our * system tables and variables. We need to describe each of our * tables as if it were a data base table. We describe each * table in general in an array of TBLDEF structures with one * structure per table, and each column of each table in an * array of COLDEF strustures with one COLDEF structure per * column. **************************************************************/ #include /* for 'offsetof' */ #include "app.h" /* for table definitions and sizes */ extern UI ui[]; extern struct MyData mydata[]; extern int compute_cdur(char *tbl, char *col, char *sql, void *pr, int rowid); extern int reverse_str(); extern void *get_next_conn(void *prow, void *it_info, int rowid); /*************************************************************** * Here is the sample application column definitions. **************************************************************/ COLDEF mycolumns[] = { { "mytable", /* the table name */ "myint", /* the column name */ RTA_INT, /* it is an integer */ sizeof(int), /* number of bytes */ offsetof(struct MyData, myint), /* location in struct */ RTA_DISKSAVE, /* save to disk */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "A sample integer in a table"}, { "mytable", /* the table name */ "myfloat", /* the column name */ RTA_FLOAT, /* it is a float */ sizeof(float), /* number of bytes */ offsetof(struct MyData, myfloat), /* location in struct */ 0, /* no flags */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "A sample float in a table"}, { "mytable", /* the table name */ "notes", /* the column name */ RTA_STR, /* it is a string */ NOTE_LEN, /* number of bytes */ offsetof(struct MyData, notes), /* location in struct */ RTA_DISKSAVE, /* save to disk */ (int (*)()) 0, /* called before read */ reverse_str, /* called after write */ "A sample note string in a table"}, { "mytable", /* the table name */ "seton", /* the column name */ RTA_STR, /* it is a string */ NOTE_LEN, /* number of bytes */ offsetof(struct MyData, seton), /* location in struct */ RTA_READONLY, /* a read-only column */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "Sample of a field computed by a write callback. Seton" " is the reverse of the 'notes' field."}, }; /*************************************************************** * One of the tables we want to present to the UI is the table * of UI connections. It is defined in app.h as .... * typedef struct { int fd; // FD of TCP conn (=-1 if not in use) int cmdindx; // Index of next location in cmd buffer char cmd[MXCMD]; // SQL command from UI program int rspfree; // Number of free bytes in rsp buffer char rsp[MXRSP]; // SQL response to the UI program int o_port; // Other-end TCP port number int o_ip; // Other-end IP address long long nbytin; // number of bytes read in long long nbytout; // number of bytes sent out int ctm; // connect time (==time();) int cdur; // duration time (== now()-ctm;) * } UI; * The following array of COLDEF describes this structure with * one COLDEF for each element in the UI strucure. **************************************************************/ COLDEF ConnCols[] = { { "UIConns", /* table name */ "fd", /* column name */ RTA_INT, /* type of data */ sizeof(int), /* #bytes in col data */ offsetof(UI, fd), /* offset from col start */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "File descriptor for TCP socket to UI program"}, { "UIConns", /* table name */ "cmdindx", /* column name */ RTA_INT, /* type of data */ sizeof(int), /* #bytes in col data */ offsetof(UI, cmdindx), /* offset from col start */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "Index of the next free byte in the input string, cmd"}, { "UIConns", /* table name */ "cmd", /* column name */ RTA_STR, /* type of data */ MXCMD, /* #bytes in col data */ offsetof(UI, cmd), /* offset from col start */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "Input command from the user interface program. This" " is an SQL command which is executed against the data" " in the application."}, { "UIConns", /* table name */ "rspfree", /* column name */ RTA_INT, /* type of data */ sizeof(int), /* #bytes in col data */ offsetof(UI, rspfree), /* offset from col start */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "Index of the next free byte in the response string, rsp"}, { "UIConns", /* table name */ "rsp", /* column name */ RTA_STR, /* type of data */ 50, /* first 50 bytes of response field */ offsetof(UI, rsp), /* offset from col start */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "Response back to the calling program. This is used to" " store the result so that the write() does not need to" " block"}, { "UIConns", /* table name */ "o_port", /* column name */ RTA_INT, /* type of data */ sizeof(int), /* #bytes in col data */ offsetof(UI, o_port), /* offset from col start */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "The IP port of the other end of the connection"}, { "UIConns", /* table name */ "o_ip", /* column name */ RTA_INT, /* type of data */ sizeof(int), /* #bytes in col data */ offsetof(UI, o_ip), /* offset from col start */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "The IP address of the other end of the connection" " cast to an int."}, { "UIConns", /* table name */ "nbytin", /* column name */ RTA_LONG, /* type of data */ sizeof(long long), /* #bytes in col data */ offsetof(UI, nbytin), /* offset from col start */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "Number of bytes received on this connection"}, { "UIConns", /* table name */ "nbytout", /* column name */ RTA_LONG, /* type of data */ sizeof(long long), /* #bytes in col data */ offsetof(UI, nbytout), /* offset from col start */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "Number of bytes sent out on this connection"}, { "UIConns", /* table name */ "ctm", /* column name */ RTA_INT, /* type of data */ sizeof(int), /* #bytes in col data */ offsetof(UI, ctm), /* offset from col start */ RTA_READONLY, /* Flags for read-only/disksave */ (int (*)()) 0, /* called before read */ (int (*)()) 0, /* called after write */ "Connect TiMe. The unix style time() when the connection" " was established. This is used to decide which connection" " to drop when the connection table is full and a new UI" " connection request arrives."}, { "UIConns", /* table name */ "cdur", /* column name */ RTA_INT, /* type of data */ sizeof(int), /* #bytes in col data */ offsetof(UI, cdur), /* offset from col start */ RTA_READONLY, /* Flags for read-only/disksave */ compute_cdur, /* called before read */ (int (*)()) 0, /* called after write */ "Connect DURation. The number of seconds the connection" " has been open. A read callback computes this each time" " the value is used."}, }; /*************************************************************** * We defined all of the data structure (column defintions) * for the tables in our UI. Now define the tables themselves. **************************************************************/ TBLDEF UITables[] = { { "mytable", /* table name */ mydata, /* address of table */ sizeof(struct MyData), /* length of each row */ ROW_COUNT, /* number of rows */ (void *) NULL, /* iterator function */ (void *) NULL, /* iterator callback data */ mycolumns, /* array of column defs */ sizeof(mycolumns) / sizeof(COLDEF), /* the number of columns */ "/tmp/mysavefile", /* save file name */ "A sample application table"}, { "UIConns", /* table name */ (void *) 0, /* address of table */ sizeof(UI), /* length of each row */ 0, /* # rows in table */ get_next_conn, /* iterator function */ (void *) NULL, /* iterator callback data */ ConnCols, /* Column definitions */ sizeof(ConnCols) / sizeof(COLDEF), /* # columns */ "", /* save file name */ "Data about TCP connections from UI frontend programs"} }; int nuitables = (sizeof(UITables) / sizeof(TBLDEF)); Node-path: test/rta_client.c Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 2072 Text-content-md5: 01c1ca11dc6535d63e7517484c707b81 Content-length: 2082 PROPS-END /*************************************************************** * Run Time Access * Copyright (C) 2003-2006 Robert W Smith (bsmith@linuxtoys.org) * * This program is distributed under the terms of the GNU LGPL. * See the file COPYING file. **************************************************************/ /* * libpq sample program * gcc rta_client.c -o rta_client -lpq */ #include #include #include "pgsql/libpq-fe.h" /* libpq header file */ char cmd1[] ="UPDATE mytable SET myint=43"; char cmd2[] ="SELECT myint, myfloat, notes FROM mytable"; int main() { PGconn *conn; /* holds database connection */ PGresult *res; /* holds query result */ int i; /* generic loop counter */ /* Connect to the application */ conn = PQconnectdb("host=localhost port=8888"); if (PQstatus(conn) == CONNECTION_BAD) { fprintf(stderr, "Connection to application failed.\n"); fprintf(stderr, "%s", PQerrorMessage(conn)); exit(1); } /* send the first command */ res = PQexec(conn, cmd1); /* send the query */ if (PQresultStatus(res) != PGRES_COMMAND_OK) { fprintf(stderr, "UPDATE command failed.\n"); PQclear(res); PQfinish(conn); exit(1); } PQclear(res); /* free result */ /* send the second command */ res = PQexec(conn, cmd2); /* send the query */ if (PQresultStatus(res) != PGRES_TUPLES_OK) { fprintf(stderr, "SELECT query failed.\n"); PQclear(res); PQfinish(conn); exit(1); } /* display the results of the second command */ printf("\n"); for (i = 0; i < PQntuples(res); i++) { /* loop through all rows returned */ printf("%s\t", PQgetvalue(res, i, 0)); printf("%s\t", PQgetvalue(res, i, 1)); printf("%s", PQgetvalue(res, i, 2)); printf("\n"); } PQclear(res); /* free result */ PQfinish(conn); /* disconnect from the database */ exit(0); } Node-path: util Node-kind: dir Node-action: add Prop-content-length: 10 Content-length: 10 PROPS-END Node-path: util/pgproxy.c Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 5734 Text-content-md5: e3a98dd8081c7cc1817fefbf47a624df Content-length: 5744 PROPS-END /* -includes, defines, and forward references */ #include #include #include #include #include #include #include #include #include #include /* Maximum size of a Postgres protocol packet from the UI's */ #define BUFSZ (1000) int listen_on_port(int); int connect_to_dest(char *, int); void printbuf(char *, int); int main(int argc, char *argv[]) { fd_set rfds; /* bit masks for select statement */ int mxfd; /* Maximum FD for the select statement */ int proxy_port; int dest_port; int proxy_srvfd; int proxy_fd; int dest_fd; int incnt; int outcnt; int adrlen; /* length of an inet socket address */ struct sockaddr_in cliskt; /* socket to the proxy client */ char buf[BUFSZ]; if ((argc != 3) || (sscanf(argv[1], "%d", &proxy_port) != 1) || (sscanf(argv[2], "%d", &dest_port) != 1)) { fprintf(stderr, "usage: %s proxy_port dest_port\n", argv[0]); exit(1); } dest_fd = connect_to_port("127.0.0.1", dest_port); proxy_srvfd = listen_on_port(proxy_port); /* wait here for a connection on our proxy port */ adrlen = sizeof(struct sockaddr_in); proxy_fd = accept(proxy_srvfd, (struct sockaddr *) &cliskt, &adrlen); if (proxy_fd < 0) { fprintf(stderr, "Unable to accept proxy connection\n"); exit(1); } while (1) { FD_ZERO(&rfds); mxfd = 0; FD_SET(proxy_fd, &rfds); mxfd = (proxy_fd > mxfd) ? proxy_fd : mxfd; FD_SET(dest_fd, &rfds); mxfd = (dest_fd > mxfd) ? dest_fd : mxfd; /* Wait for some something to do */ (void) select(mxfd + 1, &rfds, (fd_set *) 0, (fd_set *) 0, (struct timeval *) 0); /* if data from client, send to dest */ if (FD_ISSET(proxy_fd, &rfds)) { incnt = read(proxy_fd, buf, BUFSZ); if (incnt < 0) { fprintf(stderr, "Error exit on error %d\n", errno); exit(1); } else if (incnt == 0) { fprintf(stderr, "Done.\n"); exit(0); } outcnt = write(dest_fd, buf, incnt); if (outcnt != incnt) { fprintf(stderr, "Error writing to destination\n"); exit(1); } printf("from client to server: %d\n", outcnt); printbuf(buf, outcnt); } /* if data from dest, send to client */ if (FD_ISSET(dest_fd, &rfds)) { incnt = read(dest_fd, buf, BUFSZ); if (incnt < 0) { fprintf(stderr, "Error exit on error %d\n", errno); exit(1); } else if (incnt == 0) { fprintf(stderr, "Done.\n"); exit(0); } outcnt = write(proxy_fd, buf, incnt); if (outcnt != incnt) { fprintf(stderr, "Error writing to proxy client\n"); exit(1); } printf("from server to client: %d\n", outcnt); printbuf(buf, outcnt); } } } int connect_to_port(char *dest_ip, int dest_port) { int fd; struct sockaddr_in skt; int adrlen; int flags; adrlen = sizeof(struct sockaddr_in); (void) memset((void *) &skt, 0, (size_t) adrlen); skt.sin_family = AF_INET; skt.sin_addr.s_addr = inet_addr(dest_ip); skt.sin_port = htons(dest_port); if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { fprintf(stderr, "Unable to get socket to destination\n"); exit(1); } if (connect(fd, (struct sockaddr *) &skt, adrlen) < 0) { fprintf(stderr, "Could not connect to destination\n"); exit(1); } return (fd); } /********************************************************************* * listen_on_port(int port): - Open a socket to listen for incoming * TCP connections on the port given. Return the file * descriptor if OK, and -1 on any error. The calling * routine can handle any error condition. * * Input: The interger value of the port number to bind to * Output: The file descriptor of the socket * Affects: none *********************************************************************/ int listen_on_port(int port) { int srvfd; /* FD for our listen server socket */ struct sockaddr_in srvskt; int adrlen; int flags; adrlen = sizeof(struct sockaddr_in); (void) memset((void *) &srvskt, 0, (size_t) adrlen); srvskt.sin_family = AF_INET; srvskt.sin_addr.s_addr = INADDR_ANY; srvskt.sin_port = htons(port); if ((srvfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { fprintf(stderr, "Unable to get socket for port %d.", port); exit(1); } flags = fcntl(srvfd, F_GETFL, 0); /* uncomment for non-blocking: flags |= O_NONBLOCK; */ (void) fcntl(srvfd, F_SETFL, flags); if (bind(srvfd, (struct sockaddr *) &srvskt, adrlen) < 0) { fprintf(stderr, "Unable to bind to port %d\n", port); exit(1); } if (listen(srvfd, 1) < 0) { fprintf(stderr, "Unable to listen on port %d\n", port); exit(1); } return (srvfd); } void printbuf(char *buf, int cnt) { int i; char strng[17]; char hex[49]; int pos; for (i = 0; i < cnt; i++) { pos = i % 16; sprintf(&hex[pos * 3], " %02x", (0x00ff & (int) buf[i])); strng[pos] = (isprint(buf[i])) ? buf[i] : '.'; if ((i + 1) % 16 == 0) { strng[16] = (char) 0; hex[48] = (char) 0; printf("%s %s\n", hex, strng); } } if (((i % 16) != 0) || (cnt < 16)) { for (pos++; pos < 16; pos++) { sprintf(&hex[pos * 3], " "); strng[pos] = ' '; } strng[16] = (char) 0; hex[48] = (char) 0; printf("%s %s\n", hex, strng); } } Node-path: util/proto73 Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 4443 Text-content-md5: 5eb21f0beb11a9c5a718fcaa0602dd78 Content-length: 4453 PROPS-END [root@www util]# psql -h localhost -p 7777 from client to server: 296 00 00 01 28 04 d2 16 2f 00 00 00 00 00 00 00 00 ...(.../........ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 ........ from server to client: 1 4e N from client to server: 296 00 00 01 28 00 02 00 00 72 6f 6f 74 00 00 00 00 ...(....root.... 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 72 6f 6f 74 00 00 00 00 ........root.... 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 ........ from server to client: 6 52 00 00 00 00 5a R....Z from client to server: 45 51 62 65 67 69 6e 3b 20 73 65 6c 65 63 74 20 67 Qbegin; select g 65 74 64 61 74 61 62 61 73 65 65 6e 63 6f 64 69 etdatabaseencodi 6e 67 28 29 3b 20 63 6f 6d 6d 69 74 00 ng(); commit. from server to client: 79 43 42 45 47 49 4e 00 50 62 6c 61 6e 6b 00 54 00 CBEGIN.Pblank.T. 01 67 65 74 64 61 74 61 62 61 73 65 65 6e 63 6f .getdatabaseenco 64 69 6e 67 00 00 00 00 13 00 20 ff ff ff ff 44 ding...... ....D 80 00 00 00 0d 53 51 4c 5f 41 53 43 49 49 43 53 .....SQL_ASCIICS 45 4c 45 43 54 00 43 43 4f 4d 4d 49 54 00 5a ELECT.CCOMMIT.Z from client to server: 79 51 42 45 47 49 4e 3b 20 53 45 4c 45 43 54 20 75 QBEGIN; SELECT u 73 65 73 75 70 65 72 20 46 52 4f 4d 20 70 67 5f sesuper FROM pg_ 63 61 74 61 6c 6f 67 2e 70 67 5f 75 73 65 72 20 catalog.pg_user 57 48 45 52 45 20 75 73 65 6e 61 6d 65 20 3d 20 WHERE usename = 27 72 6f 6f 74 27 3b 20 43 4f 4d 4d 49 54 00 'root'; COMMIT. from server to client: 60 43 42 45 47 49 4e 00 50 62 6c 61 6e 6b 00 54 00 CBEGIN.Pblank.T. 01 75 73 65 73 75 70 65 72 00 00 00 00 50 ff ff .usesuper....P.. 00 00 00 1d 44 ff 00 00 00 05 66 43 53 45 4c 45 ....D.....fCSELE 43 54 00 43 43 4f 4d 4d 49 54 00 5a CT.CCOMMIT.Z Welcome to psql 7.3.2, the PostgreSQL interactive terminal. Type: \copyright for distribution terms \h for help with SQL commands \? for help on internal slash commands \g or terminate with semicolon to execute query \q to quit Node-path: util/proto74 Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 3659 Text-content-md5: f79632e3118347dc5c27976ac6c1192f Content-length: 3669 PROPS-END ./a.out 2345 5432 from client to server: 37 00 00 00 25 00 03 00 00 75 73 65 72 00 70 6f 73 ...%....user.pos 74 67 72 65 73 00 64 61 74 61 62 61 73 65 00 74 tgres.database.t 65 73 74 00 00 est.. from server to client: 164 52 00 00 00 08 00 00 00 00 53 00 00 00 1e 63 6c R........S....cl 69 65 6e 74 5f 65 6e 63 6f 64 69 6e 67 00 53 51 ient_encoding.SQ 4c 5f 41 53 43 49 49 00 53 00 00 00 17 44 61 74 L_ASCII.S....Dat 65 53 74 79 6c 65 00 49 53 4f 2c 20 4d 44 59 00 eStyle.ISO, MDY. 53 00 00 00 14 69 73 5f 73 75 70 65 72 75 73 65 S....is_superuse 72 00 6f 6e 00 53 00 00 00 17 73 65 72 76 65 72 r.on.S....server 5f 76 65 72 73 69 6f 6e 00 37 2e 34 00 53 00 00 _version.7.4.S.. 00 23 73 65 73 73 69 6f 6e 5f 61 75 74 68 6f 72 .#session_author 69 7a 61 74 69 6f 6e 00 70 6f 73 74 67 72 65 73 ization.postgres 00 4b 00 00 00 0c 00 00 36 94 56 f4 8d 68 5a 00 .K......6.V..hZ. 00 00 05 49 ...I from client to server: 38 51 00 00 00 25 73 65 6c 65 63 74 20 2a 20 66 72 Q...%select * fr 6f 6d 20 70 67 5f 74 61 62 6c 65 73 20 6c 69 6d om pg_tables lim 69 74 20 32 3b 00 it 2;. from server to client: 352 54 00 00 00 b2 00 06 73 63 68 65 6d 61 6e 61 6d T......schemanam 65 00 00 00 41 35 00 01 00 00 00 13 00 40 ff ff e...A5.......@.. ff ff 00 00 74 61 62 6c 65 6e 61 6d 65 00 00 00 ....tablename... 41 35 00 02 00 00 00 13 00 40 ff ff ff ff 00 00 A5.......@...... 74 61 62 6c 65 6f 77 6e 65 72 00 00 00 41 35 00 tableowner...A5. 03 00 00 00 13 00 40 ff ff ff ff 00 00 68 61 73 ......@......has 69 6e 64 65 78 65 73 00 00 00 41 35 00 04 00 00 indexes...A5.... 00 10 00 01 ff ff ff ff 00 00 68 61 73 72 75 6c ..........hasrul 65 73 00 00 00 41 35 00 05 00 00 00 10 00 01 ff es...A5......... ff ff ff 00 00 68 61 73 74 72 69 67 67 65 72 73 .....hastriggers 00 00 00 41 35 00 06 00 00 00 10 00 01 ff ff ff ...A5........... ff 00 00 44 00 00 00 47 00 06 00 00 00 12 69 6e ...D...G......in 66 6f 72 6d 61 74 69 6f 6e 5f 73 63 68 65 6d 61 formation_schema 00 00 00 0c 73 71 6c 5f 66 65 61 74 75 72 65 73 ....sql_features 00 00 00 08 70 6f 73 74 67 72 65 73 00 00 00 01 ....postgres.... 66 00 00 00 01 66 00 00 00 01 66 44 00 00 00 52 f....f....fD...R 00 06 00 00 00 12 69 6e 66 6f 72 6d 61 74 69 6f ......informatio 6e 5f 73 63 68 65 6d 61 00 00 00 17 73 71 6c 5f n_schema....sql_ 69 6d 70 6c 65 6d 65 6e 74 61 74 69 6f 6e 5f 69 implementation_i 6e 66 6f 00 00 00 08 70 6f 73 74 67 72 65 73 00 nfo....postgres. 00 00 01 66 00 00 00 01 66 00 00 00 01 66 43 00 ...f....f....fC. 00 00 0b 53 45 4c 45 43 54 00 5a 00 00 00 05 49 ...SELECT.Z....I from client to server: 47 51 00 00 00 2e 73 65 6c 65 63 74 20 74 61 62 6c Q....select tabl 65 6e 61 6d 65 73 20 66 72 6f 6d 20 70 67 5f 74 enames from pg_t 61 62 6c 65 73 20 6c 69 6d 69 74 20 36 3b 00 ables limit 6;. from server to client: 102 45 00 00 00 5f 53 45 52 52 4f 52 00 43 34 32 37 E..._SERROR.C427 30 33 00 4d 63 6f 6c 75 6d 6e 20 22 74 61 62 6c 03.Mcolumn "tabl 65 6e 61 6d 65 73 22 20 64 6f 65 73 20 6e 6f 74 enames" does not 20 65 78 69 73 74 00 46 70 61 72 73 65 5f 65 78 exist.Fparse_ex 70 72 2e 63 00 4c 31 30 33 34 00 52 74 72 61 6e pr.c.L1034.Rtran 73 66 6f 72 6d 43 6f 6c 75 6d 6e 52 65 66 00 00 sformColumnRef.. 5a 00 00 00 05 49 Z....I from client to server: 5 58 00 00 00 04 X.... Done. Node-path: util/proto74ssl Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 1369 Text-content-md5: 206006be62db3fe35456c871ee1fd7dc Content-length: 1379 PROPS-END The client opens with a request for an SSL session and the server responds with an 'N' to indicate that SSL sessions are not yet supported. Note that '04 d2' and '16 2f' are 1234 and 5678 respectively. Opening with an SSL request seems to us like a 'good thing'. Maybe someday we will be able to add SSL support to RTA. [bsmith@pohl util]$ ./a.out 9999 5432 from client to server: 8 00 00 00 08 04 d2 16 2f ......./ from server to client: 1 4e N from client to server: 33 00 00 00 21 00 03 00 00 75 73 65 72 00 72 6f 6f ...!....user.roo 74 00 64 61 74 61 62 61 73 65 00 72 6f 6f 74 00 t.database.root. 00 . from server to client: 128 45 00 00 00 7f 53 46 41 54 41 4c 00 43 32 38 30 E....SFATAL.C280 30 30 00 4d 6e 6f 20 70 67 5f 68 62 61 2e 63 6f 00.Mno pg_hba.co 6e 66 20 65 6e 74 72 79 20 66 6f 72 20 68 6f 73 nf entry for hos 74 20 22 31 32 37 2e 30 2e 30 2e 31 22 2c 20 75 t "127.0.0.1", u 73 65 72 20 22 72 6f 6f 74 22 2c 20 64 61 74 61 ser "root", data 62 61 73 65 20 22 72 6f 6f 74 22 00 46 61 75 74 base "root".Faut 68 2e 63 00 4c 34 35 32 00 52 43 6c 69 65 6e 74 h.c.L452.RClient 41 75 74 68 65 6e 74 69 63 61 74 69 6f 6e 00 00 Authentication.. Done.