|
|
IBPhoenix Development |
|
Using the InterBase Services Manager | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
By Mark Duquette, Borland Developers Conference 1999.IntroductionThe InterBase services manager is a new feature added to the latest version of the product. The services manager allows developers to program administrative functions (i.e. backup, restore, and user management) directly into their applications. Prior to the existence of the services manager, it would have been necessary to perform these functions either using the command line utilities by themselves or having the application execute the utility. This paper will describe how the services manager works as well as how to integrate it into new InterBase 6.0 applications. The objective of this paper is to outline the basics of using this new feature and to present ideas of how it can be used to enhance existing or new InterBase applications. How It WorksNormally when using the InterBase API, a connection is made to a specific database. From this connection, information can be retrieved. The services manager works outside the realm of databases. This allows an application to make a connection directly to the server without first making a connection to a database. Because of this, is is possible to get information about the server itself as well as initiate actions on databases. The services manager makes use of two type of services: passive services and active services. By using these services effectively, applications can easily administer a server and customize its interface based on the server's version and capabilities. Passive ServicesPassive services retrieve information from the server. They do not require the server to start a thread to perform some kind of action. Passive services can be started using the API call isc_service_query and will not return from the server until the request is processed or a timeout period has expired. When the server receives a request for a passive service, it simply locates the service requested, retrieves the required information, and places the information in the output buffer sent in via the API call. The information returned by passive services fall into the following three categories. Static information This information does not change based on the state of the server since it is usually a static element inside the server. Different types of static information which can be retrieved from the server include:
Dynamic information Dynamic information changes based on the state of the InterBase server. When requesting dynamic information from the server, it is important to keep in mind that the service query call will return the information based on when the server started processing the request. Any changes made while the request is being processed may not be reflected in the return from a particular request. Dynamic information which can be returned from the server includes:
Dynamic information can be used in conjunction with existing InterBase API calls to return information such as the users connected to a particular database, and with the active services to perform administrative tasks such as adding additional user licenses to the server and retrieving log file information. Active Service Information A number of the services which can be started instruct the server to perform some action. These services are known as active services and will be discussed next. When requesting information about an active service, it is important to remember to start the service first (otherwise, your requests for the information will fail). Information which can be retrieved from the server when combining active and passive services include:
Active ServicesActive services are used to request that the server start a worker thread to perform a particular kind of action. Active services are started using the API call isc_service_start and will return control back to the client from the server once the thread has been started or on an error condition. When the server receives a request to start an active service, it locates the service requested, parses the service parameter buffer, and starts the thread specified by the service action call. The services manager only allows one active service to be running at a time per attachment to the services manager. Because of this, active services are usually started in the following manner:
The services which can be started using isc_service_start mirror the functionality of the command-line tools gbak, gstat, gfix, and gsec. The following tasks can be performed using active services:
SecurityThe security context used by the services manager is determined by the information passed in when connecting to the services manager. When connecting to the services manager, the input parameter block will specify a username and a password. This information determines if the username and password combine to make a valid InterBase user and determines if the user is the SYSDBA user. This base security context is used for all services, active or passive, which are executed using this connection. Some of the services can only be accessed by the SYSDBA user, for example adding license certificates, however many services only require a valid login to the InterBase server. Most of the passive services can be accessed by any valid InterBase user and many of the active services take an optional role name as a parameter if the service acts on a database, for example modifying user information in the security database. Working With ServicesWhen working with services, the most important thing to remember is that everything done by the services is with respect to the server and its environment. This is particularly important when using the active services. Working with services is similar to using the InterBase API to work with databases and transactions. Note: #define ADD_SPB_LENGTH (p, length) {*p++ = length;
*p++ = length >> 8;}
#define ADD_SPB_NUMERIC (p, data){*p++ = data;
*p++ = data >> 8;
*p++ = data >> 16;
*p++ = data >> 24;}
When parsing a buffer which has information returned from the server via a call to isc_service_query, discussed later, the same restriction applies. Use the existing InterBase API call isc_vax_integer to process the length and numeric data information. Connecting To The Services ManagerThe first step in working with the services manager is connecting to it. This is done using the API call isc_service_attach which is defined as follows: ISC_STATUS ISC_EXPORT isc_service_attach (ISC_STATUS *status_vector,
ISC_USHORT service_length,
char *service,
isc_svc_handle *svc_handle,
ISC_USHORT spb_length,
char *SPB)
returns: value of ISC_STATUS[1] :: 0 if no error
>0 if there is an error
*status_vector: pointer to a 20 element array of ISC_STATUS
service_length: length in characters of the name of the service and host to
connect to.
Specify 0 if the service is a null terminated string.
*service: pointer to a character buffer containing the name of the host to
connect to.
*svc_handle: pointer to a long value used to store the handle of the service
structure
on the server.note: This value MUST be 0L when attaching to a service.
spb_length: length in bytes of the service parameter buffer
*SPB: pointer to a buffer containing the service parameter
To specify which server to connect to, set the service parameter to one of the following connection strings
The literal string service_mgr is required in all instances. Replace serverhost with the name of the actual server that will be used for the connection. Before initiating a connection to the services manager, the SPB must be filled out with the username and password to be used for the connection. The username and password used to connect will determine access to other services. Once the connection is made, use the svc_handle to start or query services and to disconnect from the services manager. An example of how to connect to the services manager via TCP/IP char *user = "SYSDBA",
*pass = "masterkey";
ISC_STATUS status [20];
isc_svc_handle *svc_handle = NULL;
char svc_name[32],
spb_buff[128],
*SPB = spb_buff,;
ISC_USHORT spblen;
*SPB++ = isc_spb_version;
*SPB++ = isc_spb_current_version;
*SPB++ = isc_spb_user_name;
*SPB++ = strlen (user);
strcpy (SPB, user);
SPB += strlen (user);
*SPB++ = isc_spb_password;
*SPB++ = strlen (pass);
strcpy (SPB, pass);
SPB += strlen (pass);
sprintf (svc_name, "myserver:service_mgr"); /* TCP/IP */
spblen = SPB - spb_buff;
if (isc_service_attach (status, 0, svc_name, &svc_handle, spblen, spb_buff))
{
isc_print_status (status);
exit (1);
}
The user parameters accepted in the SPB for isc_service_attach are:
Starting ServicesOnce a client has attached to the services manager using isc_service_attach, the client uses this API call to start an active service on the server. Only one active service can be started at a time per service handle. An error will result if an attempt is made to start more than one service on the server using the same service handle. The API call for starting a service, isc_service_start is defined as follows: ISC_STATUS ISC_EXPORT isc_service_start(ISC_STATUS *status_vector,
isc_svc_handle *svc_handle,
isc_resv_handle *reserved,
ISC_USHORT spb_length,
char *SPB);
returns: value of ISC_STATUS[1] :: 0 if no error
*status_vector: pointer to a 20 element array of ISC_STATUS
*svc_handle: pointer to a long value used to store the handle of the service
structure
on the server. This must point to a handle previously allocated in
isc_service_attach.
*reserved: this parameter is being reserved for future use
spb_length: length in characters of the send buffer
*SPB: pointer to a buffer containing flags instructing the service manager
to process information
For each service action, there is a list of parameters which define that action. The parameters consist of three different pieces of information: the parameter name, length, and value. The length and value are specific to the individual parameters. The services which can be started using isc_service_start are
When the server receives a request to start a service thread, the following process occurs:
It is very important to keep in mind that the service is executing based on the current server environment and not the client environment. This means that any database or file parameters specified must be done with respect to the server. The return value and status from isc_service_start represents any error that could have occurred either in the process of launching the service thread or from the service itself. For example, the database to backup could not be opened. The function,isc_service_start, will not return until the service has completed its initialization and is actually running. The following is an example of starting the service to retrieve the contents of the interbase.log file. It assumes that a connection has already been made using isc_service_attach. ISC_STATUS status [20];
char thd_buff[256], *thd = thd_buff;
ISC_USHORT thdlen;
/* isc_service_attach happens here */
*thd++ = isc_action_svc_get_ib_log;
thdlen = thd - thd_buff;
if (isc_service_start(status, &svc_handle, NULL, thdlen, thd_buff))
{
isc_print_status (status);
isc_service_detach (status, &svc_handle);
exit(1);
}
Most of the active services return some type of information back to the client. The way this is accomplished is via a call to isc_service_query, which will be discussed next. The information from the service thread is stored in an internal buffer in the server. This buffer is currently set to hold 1k worth of information. If, while the service is writing to this internal buffer, the buffer becomes filled, the service thread will wait until there is more room in the buffer. In order for the buffer to be emptied, the client must query this information. However, if, while the service is running the client disconnects, the service will stop writing to the internal buffer, but will complete its operation. There is currently no way to cancel a running service. Querying ServicesIn addition to starting active services on the server using isc_service_start, a client can start a passive service using the API call isc_service_query once a successful connection has been made to the services manager via isc_service_attach. The client uses this API call to retrieve information about the server, such as the installed licenses or to return information from a currently running active service. This is accomplished by specifying the correct parameters in the receive buffer specified in the API call which is defined below ISC_STATUS ISC_EXPORT isc_service_query (ISC_STATUS *status_vector,
isc_svc_handle *svc_handle,
isc_resv_handle *reserved,
ISC_USHORT send_spb_length,
char *send_spb,
ISC_USHORT recv_spb_length,
char *recv_spb,
ISC_USHORT response_buffer_length,
char *response_buffer);
returns: value of ISC_STATUS[1] :: 0 if no error
>0 if there is an error
*status_vector: pointer to a 20 element array of ISC_STATUS *svc_handle: pointer to a long value used to store the handle of the service structure on the server. This must point to a handle previously allocated in isc_service_attach. *reserved: this parameter is being reserved for future use send_spb_length: length in characters of the send buffer *send_spb: pointer to a buffer containing flags instructing the service manager to process information recv_spb_length: length in characters of the receive buffer *recv_spb: pointer to a buffer containing flags instructing the service manager to return information response_buffer_length: length in characters of the return buffer *response_buffer: pointer to a buffer containing information received from the services manager The services which can be used to return information about the server and its environment are
As mentioned earlier, isc_service_query can also be used to return information about any active services which are currently running on the server. The services which retrieve information from active services are
Calls to isc_service_query can request more than one item at a time. This can be useful in reducing the number of calls to the server, however, as with anything, there are always caveats. If the call to isc_query_service is going to return more information than can fit in the response buffer, the buffer will be filled with as much information as possible and the buffer will be terminated by the flag isc_info_truncated. Since these services normally return information about the server's environment, there is no way to reissue the request and have it restart from where the last call left off. To retrieve all of the information requested, the response buffer will need to be increased and another call will have to be made. Now, the information regarding isc_info_truncated only relates to those services which do not work with active services. Detaching ServicesJust as when working with databases, always disconnect from the services manager when finished. If an application does not disconnect properly, the server will continue to hold onto the memory used by the connection until the server detects that the client is no longer attached. Once the server detects that the client is not connected, all memory held by that connection is freed. To detach from the services manager, use the API call isc_service_detach which is defined as ISC_STATUS ISC_EXPORT isc_service_detach (ISC_STATUS *status_vector,
isc_svc_handle *svc_handle);
returns: value of ISC_STATUS[1] :: 0 if no error
>0 if there is an error
*status_vector: pointer to a 20 element array of ISC_STATUS
*svc_handle: pointer to a long value used to store the handle of the
service structure on the server. This must point to a handle previously
allocated in isc_service_attach.
Working With The ResultsNow that we have discussed the mechanics involved in accessing and working with the services manager, it is time to explore how to work with the information returned from the services manager. All calls into the services manager return a status vector containing any errors which may have occurred during the call. However, in order to actually work with the information requested, a call to isc_service_query is required. As mentioned above, the service manager can be queried to return information either about the server's environment or about active services which are currently running or have just completed. Information is returned using via the response buffer specified in the call to isc_service_query. This buffer contains information in one of two formats; formatted or unformatted. Unformatted BuffersMost active services store their information as unformatted text inside the server. An example of an active service which stores its information this way is isc_action_svc_backup which is used to backup a database. When this service is initiated along with the parameter isc_spb_verbose, the backup service will store all of the output produced. This corresponds to the -v flag used with the command-line tool GBAK. The information stored in the internal buffer is then returned to the client via isc_service_query using either the parameter isc_info_svc_line or isc_info_svc_to_eof. Using the parameter isc_info_svc_line instructs the server to fill the client's buffer with no more than 1 line of information. Since a line of information is delimited by a carriage return, the response buffer for the query call does not have to accommodate 1K of information, however it may require more calls into the server. An alternative method for returning the information is to use isc_info_svc_to_eof . Using this parameter in the query call instructs the server to fill the client's response buffer with as much information as will fit. In order to make the most of the call to the server using isc_info_svc_to_eof, it is important to keep in mind that the server will store up to 1K of information at a time. In addition, it is even more important to keep in mind that once the internal buffer is full, the service will not continue until the client requests some of the information stored in the buffer. The database backup example will show how to work with an unformatted buffer returned by isc_info_svc_line. Formatted BuffersWhile most of the active services return unformatted information, which in this case is basically equivalent to a text dump, most of the passive services return information in a formatted buffer. The information in a formatted buffer is returned via a specific buffer sent in with the call to isc_service_query. The information returned has a is very specific to the parameter sent in, however, the format of the buffer is fairly generic between the different services. The basic format of the buffer is made of clumpets of data, much like an InterBase status vector or DPB buffer. The format of a service buffer is For queries that return a single piece of information (i.e. isc_info_svc_get_env) <isc_info_svc_get_env> <2 byte length> <data> <isc_info_end> For queries that return numeric information (i.e isc_info_svc_get_licensed_users) <isc_info_get_licensed_users> <4 byte data> <isc_info_end> For queries that return a clumpet of information (i.e. isc_info_svc_db_info) <isc_info_svc_db_info> <isc_spb_num_att> <4 byte data> <isc_info_spb_num_users> <4 byte data> <isc_info_spb_dbname> <2 byte length> <data> ..... (the clumpet is repeated for each database) <isc_info_spb_dbname> <2 byte length> <data> <isc_info_flag_end> <isc_info_end> Even though each call has a slightly different return buffer, they all follow the same pattern which is
Example: Database BackupTo put all of the information presented above into perspective, this example will center around performing a database backup and will use much of the information presented above. As with all services, and especially those which work with databases, it is extremely important to remember that the service is executed on the server. This example is separated into two parts. The first part performs the setup for connecting to the services manager and starting the service. In this section there will be examples of the following:
The second part of the example focuses on querying the service to provide the output for the backup. There will be specific examples of
The Setupchar *user = "SYSDBA", *pass = "masterkey", *dbname = "employee.gdb", *bkup_file = "employee.gbk"; ISC_STATUS status [20]; isc_svc_handle svc_handle = NULL; char svc_name[RESPBUF], spb_buff[RESPBUF], thd_buff[RESPBUF]; char respbuf[RESPBUF], *p = respbuf, *SPB = spb_buff, *thd = thd_buff,*x; short spblen, thdlen; int i = 0, cnt=0; boolean finished = FALSE; /* Set up the SPB for connecting to the services manager.
* in this example, we will connect to the server running
* on the local machine.*/
*SPB++ = isc_spb_version;
*SPB++ = isc_spb_current_version;
*SPB++ = isc_spb_user_name;
*SPB++ = strlen (user);
strcpy (SPB, user);
SPB += strlen(user);
*SPB++ = isc_spb_password;
*SPB++ = strlen (pass);
strcpy (SPB, pass);
SPB += strlen (pass);
sprintf (svc_name, "service_mgr");
spblen = SPB - spb_buff;
if (isc_service_attach (status, 0, svc_name, &svc_handle, spblen, spb_buff))
{
isc_print_status (status);
exit (1);
}
/* Set up the SPB for starting the backup service */ *thd++ = isc_action_svc_backup; /* Add the database name to the buffer */ *thd++ = isc_spb_dbname; ADD_SPB_LENGTH (thd, strlen(dbname)); strcpy (thd, dbname); thd += strlen (dbname); /* Add the name of the backup file to the buffer */ *thd++ = isc_spb_bkp_file; ADD_SPB_LENGTH (thd, strlen(bkup_file)); strcpy (thd, bkup_file); thd += strlen (bkup_file); /* Specify that that we want verbose output (GBAK -v) */ *thd++ = isc_spb_verbose; thdlen = thd - thd_buff; /* Start the service and process any errors that may be returned */
if (isc_service_start(status, &svc_handle, NULL, thdlen, thd_buff))
{
long *vector = status;
printf ("Unable to start service:n");
while (isc_interprete (respbuf, &vector))
printf ("ERROR: %sn", respbuf);
printf ("End of errorsn");
isc_service_detach (status, &svc_handle);
exit(1);
}
Seeing The Resultsdo {
/* Add isc_info_svc_line to the send buffer for the query */
char sendbuf[] = {isc_info_svc_line};
ISC_STATUS loc_status[20], *stat = loc_status;
/* Issue the query */
if (isc_service_query (status, &svc_handle, NULL, 0, NULL,
sizeof (sendbuf), sendbuf, RESPBUF, respbuf))
{
isc_print_status (status);
isc_service_detach (status, &svc_handle);
exit(1);
}
/* If the query is successful, start processing the return buffer */
x = p = respbuf;
if (*p++ == isc_info_svc_line)
{
ISC_USHORT len = 0, chTmp = 0;
/* Get the length of the data being returned */
len = (ISC_USHORT)isc_vax_integer(p, sizeof(ISC_USHORT));
p += sizeof (ISC_USHORT);
/* If there is no length, then we should be done .. let's check */
if (!len)
{
if (*p != isc_info_end)
printf ("Format error ... <%d>n", *p);
else
printf ("Output completedn");
break;
}
/* There is a length for the data, so to be different, let's print
* it out character by character */
for (chTmp = 0; chTmp < len; chTmp++)
printf("%c",p[chTmp]);
/* Since we only requested one item this time, we should be at the end of
* the buffer terminated by isc_info_end after moving the pointer past the
* data we just printed out */
p += len;
if (*p != isc_info_truncated && *p != isc_info_end)
{
printf ("Format error ... encountered <%d>n", *p);
break;
}
}
}
while (*x == isc_info_svc_line);
isc_service_detach(status, &svc_handle);
}
SummaryThis paper outlined the new InterBase services manager which can enable developers to program administrative functions, like backup, restore, and user management, directly into new applications. To be successful with the services API, it is important to keep the following issues in mind.
|