• Aucun résultat trouvé

Telling RTA About Your Columns and Tables

Dans le document LINUX APPLIANCE DESIGN (Page 45-50)

A table is an array or linked list of data structures. Each member of your data structure is considered to be a column in a table, and each instance of the data structure is considered to be a row. From this point on, when you see the term column, think member of my data structure. In order to make a table visible to clients, you need to describe the table in a way that RTA can understand. This means describing the table as a whole and then describing each column in the table.

A TBLDEF structure describes the table as a whole; it contains a pointer to an array of column definitions with a COLDEF structure to define each column in your data table. At first you may find the process of creating COLDEFs and TBLDEFs painstaking and tedious, but once you have a little experience, you’ll find it simple and mechanical.

Unix/TCP Socket

to UIs

dbcommand()

RTA Library rta_add_table()

Your Daemon Table A

Table B

Columns

One big advantage of RTA is that you don’t need to marshal the data into and out of the protocol. RTA uses your data as it already exists in your pro-gram. Of course, you have to describe your data so that RTA can access it intelligently. A table is made up of columns, and we need to describe each column in the table. This is the purpose of RTA’s COLDEF data structure.

You can also have members in your data structure that are not defined by a COLDEF. Such hidden columns might include information that you do not want to be visible to the UIs, or binary data that would have no meaning if it was displayed to the user.

A COLDEF contains nine pieces of information about each of your structure’s members.

typedef struct {

char *table; // name of column's table

char *name; // name of column for SQL requests int type; // data type of column

int length; // width of column in bytes

int offset; // number of bytes from start of row int flags; // flags for read-only and save-to-file void (*readcb) (); // routine to call before reading column int (*writecb) (); // routine to call after writing column char *help; // description of the column

} COLDEF;

table

The table field specifies the name of the table as seen from the UI programs.

name

The name field specifies the name of the column. Use this name when selecting or updating this column.

type

The type of the column is used for syntax checking and for SQL SELECT output formatting. The currently defined types include:

RTA_STR // string, (char *)

RTA_PTR // generic pointer (void *) RTA_INT // integer (compiler native int) RTA_LONG // long (actually a gcc 'long long') RTA_FLOAT // floating point number

RTA_PSTR // pointer to string RTA_PINT // pointer to integer RTA_PLONG // pointer to long RTA_PFLOAT // pointer to float

length

RTA uses the native compiler data types in order to match the data types you use in your structures. The length member is ignored for integers, longs, floats, and their associated pointer types, but it has meaning for strings and pointers to strings, both of which should report the number of bytes in the string (including the terminating null).

offset

The offset is the number of bytes from the start of the data structure to the structure member being described. For example, a table using a data structure with an int, a 20-character string, and a long would have the offset to the long set to 24 (assuming it was a 4-byte int).

Computing the offset of a structure member is painstaking and error prone. The gcc compiler suite provides the offsetof() macro to automatically compute the offset of the structure member.

flags

A column has two binary attributes that are specified by the flags mem-ber. The first attribute specifies whether the column can be overwritten or if it is read-only. Statistics are often marked as read-only. An error is generated if a column marked as read-only is the subject in an UPDATE statement. The #define for this attribute is RTA_READONLY.

The second attribute specifies whether or not values written to this column should be saved in a configuration file associated with the table.

Values that should persist from one invocation of the program to the next should be marked with the #define RTA_DISKSAVE attribute.

The flags field is the bitwise OR of RTA_DISKSAVE and RTA_READONLY. readcb()

If defined, the read callback routine, readcb(), is called every time the column’s value is used. This is handy for values that take lots of CPU cycles to compute but that are used infrequently. A read callback is invoked each time the column is referenced—if your SQL statement uses the column name twice, the read callback is called twice.

The read callback is passed five parameters: the table name, the column name, the text of the SQL request, a pointer to the row affected, and the zero-indexed row number. A function prototype for a read callback is shown below.

int readcb(char *tbl, char *col, char *sql, void *pr, int rowid);

A read callback returns zero on success and an error code if an error occurred in the callback. (See Appendix A for a list of the error codes and more details on callbacks.) Check the return value in your clients in order to enhance reliability and security.

writecb()

Write callbacks can be the real engine driving your application. If defined, the write callback, writecb(), is called after all columns in an UPDATE have been changed. Consider the following SQL command:

UPDATE ifcfg SET addr="192.168.1.1", mask = "255.255.255.0";

If there is a write callback on addr, it will be called after both addr and mask have been updated. RTA does the write callback after all the fields have updated in order to help maintain consistency.

Write callbacks are passed six parameters: the table name, the column name, the text of the UPDATE statement, a pointer to the row affected, the zero-indexed row number, and a pointer to a copy of the row before any changes were made. (This last parameter is useful when you want to know both the old and new values for the row.) The copy of the old row is in dynamically allocated memory, which is freed after the write callback returns. A function prototype for a write callback is shown below.

int writecb(char *tbl, char *col, char *sql, void *pr, int rowid, void

*poldrow);

The write callback returns zero on success and nonzero on failure.

On failure, the row is restored to its initial value and an SQL error, TRIGGERED ACTION EXCEPTION, is returned to the client. Write callbacks allow you to enforce consistency and can provide security checks for your system.

help

Your help text for the column should include a description of how the column is used, any limits or constraints on the column, and the side effects caused by any read or write callbacks. (Give yourself and your fellow developers meaningful help text for your columns to make it easier to maintain and troubleshoot your code.)

Tables

You tell RTA about each of your tables with a call to the RTA routine rta_add_table(). The single parameter to rta_add_table() is a pointer to a TBLDEF structure that describes the table.

The TBLDEF structure uses 10 pieces of information to describe your table. The most critical of these are the name of the table, the start address of the array of structures, the width of each structure (that is, the width of each row), the number of rows, and a pointer to an array of COLDEF structures that describe the columns in the table. Most of the fields in the TBLDEF structure should be self-explanatory.

typedef struct {

char *name; // the SQL name of the table void *address; // location in memory of the table

int rowlen; // number of bytes in each row int nrows; // number of rows

void *(*iterator) (void *cur_row, void *it_info, int rowid);

void *it_info; // transparent data for the iterator call

The need to save configuration data from one boot of the appliance to the next is so common that the authors of RTA included the ability to automatically save table data in a file when the data is updated. There is one file per table, and the name of the file is specified in the TBLDEF structure as the savefile string. You can mark the columns to save by adding the RTA_DISKSAVE flag to the column definition.

The save file contains a list of UPDATE statements, one for each row in the table. The save file is read from the disk and applied to the table when you initialize the table with the rta_add_table() call. The combina-tion of RTA_DISKSAVE on a column and a savefile for the table eliminates the need to parse XML or INI files to get initial or saved configuration values. Of course, you can use XML or INI if you prefer to—just set the savefile pointer to a null.

iterator

An iterator is a subroutine in your code that steps through a linked list or other arrangement of the rows in your table. The iterator lets you treat a linked list, a B-tree, or just about any other scheme for organizing data as if the data was in a table.

The iterator function is called with three parameters: a pointer to the current row, the void pointer it_info from the TBLDEF, and the zero-indexed row number. The function returns a pointer to the next row. When RTA asks for the first row, the current row pointer is NULL, and the desired row index is zero. The function should return a NULL when RTA asks for the row after the last row in the list. If an iterator is defined, the address and nrows members in the TBLDEF are ignored. Here is its function prototype.

void iterator(void *cur_row, void *it_info, int rowid);

There is one caveat when using iterator functions: Loading a save file may fail if you have not already allocated all the links in the linked list. (Remember, the save file is a list of UPDATE statements and expects the rows to already exist.) Fortunately, there is a simple way around this problem. Always keep one unused row available, and when that row is written by an UPDATE statement, have a write callback allocate another row so that you can stay one step ahead of the UPDATEs. The logmuxd program presented in Chapter 7 uses this technique.

Dans le document LINUX APPLIANCE DESIGN (Page 45-50)