InterBase Installation API - Design Specification

High-Level Design

As mentioned in the Functional Specification the Install API will, at first, only be implemented for Windows 95 and Windows NT 4.0. Furthermore, we know that the way the InterBase products are installed on windows will always be different than the way they are installed on the UNIX platforms. Even though the overall design of this API should not restrict its implementation, we understand that the implementation will be different for Windows than it will be on the UNIX platforms. For these reasons we will be able to take advantage of the Microsoft Setup API for the first implementation of our Install API.

High Level Algorithm

The Microsoft Setup API provides functionality for creating sets of files to be copied, along with the conditions upon which to perform the copy operation. This includes whether to over-write an existing file or to perform version checking. In addition the Microsoft Setup API provides logging functionality to record all actions taken by the Setup API. Even though the Microsoft Setup API is quite powerful, we will still need to write all the logic needed to install the products in such a way that we can install the InterBase product.

We will also need to write an abstraction layer to isolate the generic install logic from the platform specific installation code, such as copying files and checking for registry keys. This is done to facilitate the future implementation of the Install API on other platforms. This means that the functions that determine which InterBase product we are installing, and which options are valid will in turn call platform specific functions to actually perform the precheck and the installation.

The high level algorithm for the eight API functions is as follows:

isc_install_set_option (phandle, option)

The first time this function is called handle should be set to null by the caller. This indicates that the options list is not allocated. Each time this function is called, it will allocate memory for a new option node, set the values of this node to contain the value of option, and add it to the linked list of options. The head of this linked list is pointed to by handle, and is maintained by the install engine. Each function will in turn be passed the address of handle which will be used to determine the options requested.

Note

This design prepares us for a multi-threaded install engine, although no effort will be spent on implementing this at this time. Furthermore, the user can call this function multiple times with handle set to 0L to allocate multiple options list.

isc_install_unset_option (phandle, option)

This function will traverse the options list to find and remove the option specified by option. If option is the only option in the list, will be set to 0L to indicate an empty options list. If handle is set to 0L when this function is called, a warning will be generated to indicate that the options list is empty.

isc_install_clear_options (phandle)

This function will clear all the options stored in handle. This needs to be called anytime the options need to be rebuilt, and may be called at the end of the install process. Upon successful completion handle is set to 0L. If handle is already 0L then a warning will be returned since the handle has not been allocated.

isc_install_precheck (handle, source_path, dest_path)

This function will perform all the pre install checks needed. These are:

  1. Check that we are running on a valid operating system. Currently these are Windows 95, Windows 98, and Windows NT 4.0, if these are not detected, return an error.
  2. Check that the source_path and dest_path are valid, if not return an error. This check is skipped for source_path if it is NULL or an empty string, otherwise it is checked that the correct permissions exist for reading from source_path and it is a directory . dest_path is only checked when it is specified by the user and it is not NULL. dest_path is checked that it is not on a network drive and that it is a writeable directory. It is also checked for the availability of the necessary disk space for the options requested on dest_path and for the files which must go into the windows system directory. If any of the checks fail the error is immediately returned. Not all of the checks are performed when the directory does not actually exists.
  3. Check for dependencies between options requested. The dependencies between the IB_ options are as follows:
    • The IB_CMD_TOOLS, IB_GUI_TOOLS, IB_DEV, and IB_ODBC options need the IB_CLIENT option to function correctly. This also applies to all individual IB_CMD_TOOLS_ and IB_GUI_TOOLS_ options. Warning is returned.
    • The IB_EXAMPLES option needs the IB_CLIENT and the IB_DEV options to function correctly. Warning is returned.
    • Check to see if a Classic server is present, if so return an error.
    • Check for a Super server already running. If one is already running, return an error. This check is skipped if the IB_SERVER option is not specified.
    • Check for correct user permissions needed to install the server service to the Windows Services Manages on NT, and for the addition of registry entries. If the user running the install does not have Administrator privileges, then return an error. This check is skipped if the IB_SERVER option is not specified.

Some of the dependencies may return errors others may return warnings. It is up to the user to install when a warning is returned. The install will not proceed if isc_install_precheck returns an error.

isc_install_execute(handle, source_path, dest_path, fp_status, status_arg, fp_error, error_arg, uninstall_file_name)

This function will perform the actual install, that is:

  1. Call isc_install_precheck to ensure that the install may be performed. If isc_install_precheck returns an error the install is aborted.
  2. Log all actions to a file called ib_install.log in a temporary location.
  3. Create the destination directory if it does not already exist.
  4. Copy the files using all the correct version checks, and delayed copying methods necessary.
  5. Increase the reference count of shared files.
  6. Create the registry entries needed.
  7. Install the guardian in the Services Manager on Win NT, or add the guardian to the Run section of the registry on Win 95.
  8. Modify the TCP/IP services file if it exists.
  9. Stream options into ib_uninst.xxx (where xxx is a sequence number) for use at uninstall time.
  10. Free the options list from memory.
  11. Move ib_install.log to the install directory.
  12. Call fp_status at regular intervals to keep caller appraised of status.
  13. If at any point in the install is canceled (by the user, or by an error) attempt to cleanup.

isc_uninstall_precheck(uninstall_file_name)

This function will perform all pre uninstall checking needed, such as:

  1. Check that we run on supported OS version.
  2. Check that the uninstall file (ib_uninst.xxx) contains the streamed list of options.
  3. Check that the server is not running if the server was installed. Otherwise this check is skipped
  4. Check for Administrator permissions which are needed to uninstall Server (NT only).

isc_uninstall_execute(uninstall_file_name, fp_status, status_arg, fp_error, error_arg)

This function will perform the actual uninstall, that is:

  1. Call isc_uninstall_precheck to ensure that the uninstall may be performed.
  2. Decrease reference counts of shared files, and remove any which have a reference count less than 1, except for those file which Microsoft has pre assigned to 0, such as MSVCRT.DLL.
  3. Remove all InterBase files which are in the ib_uninst.xxx file, except for isc4.gdb, isc4.gbk, ib_license.dat
  4. Remove registry entries which are in ib_uninst.xxx.
  5. Uninstall the Guardian and Server service with the Services Manage on NT. Remove Run registry entries on 95-98.
  6. Call fp_status at regular intervals to keep caller appraised of status.
  7. If at any point in the uninstall is canceled by the user, or by an error attempt to cleanup.

isc_install_get_message (handle, msg_no, msg, msg_len)

This function converts the error/warning value stored in msg_no and returns the corresponding error/warning message for use by the programmer. Currently there is no OS specific error message returned.

isc_install_load_exetrnal_text (external_path)

The function is designed to load an .msg file with customer defined textual information . The file is to be created by a customer using our MAKEMSG utility.

MSG_NO isc_install_get_info( info_type, option, info_buf, buf_size);

The function will return the information requested by info_type. If the info_type is not option specific then option is ignored. The information is returned at info_buf location. This parameter can not be NULL. A proper buf_size in bytes should be specified. If either info_buf or buf_size are NULL(0) an error will be generated. Currently 4 types of the information can be expected from the function but it is possible to add additional ones as necessary. The types of information are:

  • Suggested destination directory. This functionality is taken over from isc_install_precheck. isc_install_precheck will no longer suggest a destination path (Option parameter is ignored in this case).
  • Disk space required to install a particular option (Requires a valid option)
  • A human readable option name (Requires a valid option)
  • A human readable option description. (Requires a valid option)

Data Structures/Class Hierarchy

This feature does not have any external data structures, however it does have some typedef's for the convenience of the user. These are:

typedef void* OPTIONS_HANDLE This will essentially contain a pointer to a internal data structure with all necessary context information.
typedef long MSG_NO
typedef unsigned long OPT
typedef char TEXT
typedef int (*fp_error)() FP_ERROR
typedef int (*fp_status)() FP_STATUS
typedef long STRING_ID is essentially a synonym type for MSG_NO. The STRING_ID will be used to uniquely identify the textual string and to retrieve it using TXT_get_text. We need to define a new way to store text messages that will differ from the way we store them in the version 5.5 of the Install API. This is because we now might have not only the default text strings but also customized text strings defined by the customer. To accomplish a reliable processing of the customer defined text strings we need to carry out various checks when creating messages from a text file created by the customer. We also want to retain a flexible way of adding textual messages should we add a new installable option with corresponding textual name and description or error/warning strings.
typedef char TEXTSTR[ISC_INSTALL_MAX_MESSAGE_LEN]
typedef TEXTSTR* PTEXTSTR

Interfaces

The user interface for this feature is already listed in the functional spec., since this feature is an API. The only other interface is the list of defines for the options, and the error message numbers. The list of options is a follows:

INTERBASE 1000
IB_SERVER 1001
IB_CLIENT 1002
IB_CMD_TOOLS 1003
IB_CMD_TOOLS_DB_MGMT 1004
IB_CMD_TOOLS_USR_MGMT 1005
IB_CMD_TOOLS_DB_QUERY 1006
IB_GUI_TOOLS 1007
IB_GUI_TOOLS_DB_QUERY 1008
IB_GUI_TOOLS_USR_MGMT 1009
IB_GUI_TOOLS_DB_MGMT 1010
IB_DOC 1011
IB_EXAMPLES 1012
IB_DEV 1013
IB_ODBC 1014

isc_isntall_get_info interfaces are defined as follows

isc_install_info_destination Request the function to suggest the destination directory
isc_install_info_opname Requests the function to return the human readable name of the option
isc_install_info_opdesc Requests the fucntion to return the human readable description of the option
isc_install_info_opspace Resquests the fucntion to return the amount of the disk space required for installation of the option specified.

Detailed Design

Internal Data Structures

There is a need to define some structures to allow us to specify what each option value means, and what actions are to be taken in order to install that option. These structures are:

A member is any one of the options above. These can be groups of groups, groups of elements, or elements.

typedef unsigned long member

A dependency is a grouping of install and uninstall check functions which should be called before performing that action, they are represented by a depend structure.

typedef struct depend {
   FP_CHECK fp_install_check;
   int  install_check_param1;
   FP_CHECK fp_uninstallcheck;
   int  uninstall_check_param1;
} *DEPEND;

A component is grouping of options which can be individually selected. That is a group of groups, a group of elements, or an individual element.

typedef struct component {
   member             component_name;  /* This is actually the option value */
   member             *component_members;
   struct depend      *component_dependencies;
   unsigned long      size;
   TEXT*              comp_disp_name;
   TEXT*              comp_description;
   ACTION             comp_actions;   /* Array of actions */
   STRING_ID    perform_text; /* This will be used as a textual description of the action during install */
   STRING_ID    unperform_text; /* This will be used as a textual description of the action during uninstall */
} COMPONENT;

An action is what is to be performed when an individual element is selected. This can include copying files, or creating registry entries.

#define    ISC_INSTALL_NUM_ACT_PARAM    3
typedef struct action {
   FP_ACTION   fp_perform;
   FP_ACTION   fp_unperform;
   TEXT*       action_param1;
   TEXT*       action_param2;
   TEXT*       action_param3;
   STRING_ID    perform_text; /* This will be used as a textual description of the action */
   STRING_ID    unperform_text;
} *ACTION;

An action_item is a node of a linked list of actions used to hold the list of actions to be performed once the list of options is parsed.

typedef struct action_item {
   ACTION                  act;
   BOOL                    ifperformed;
   struct  action_item*    act_next;
   struct  action_item*    act_prev;
} *ACTION_ITEM;

Once we combine these structures with some convenient macros, such as:

/* GROUP definitions */

#define DEFINE_GROUP(group_name)        static OPT group_name##_MEMBERS[]={
#define MEMBER(member_name)             member_name,
#define END_DEFINE_GROUP                0 };

/* Dependencies definitions */

#define DEFINE_DEPENDENCY(depend_name)  static struct depend depend_name[]={
#define CHECK_ELEMENT(element)          { UTIL_find_option, element, NULL, 0 },
#define CHECK_PRIV                      { PSC_check_permissions, 0, PSC_check_permissions, 0 },
#define CHECK_NOT_CLASSIC               { PSC_classic_not_found, 0 , NULL, 0 },
#define CHECK_SERVER_NOT_RUNNING        { PSC_server_running, 0, PSC_server_running, 0 },
#define END_DEFINE_DEPENDENCY           { NULL, 0, NULL, 0 } };

/* Components definitions */

#define COMPONENTS                      static COMPONENT components[]={
#define GROUP(group_name, name, desc)  { group_name, group_name##_MEMBERS, NULL, 0L, name, desc, NULL },
#define ELEMENT(el_name, depends, size, name, desc) { el_name, NULL, depends, size, name, desc, el_name##_ACTIONS },
#define END_COMPONENTS                  { 0 , NULL, NULL, 0L, 0L, 0L, NULL } };

/* Actions definitions */

#define ACTIONS(actionset_name)
static struct action actionset_name##_ACTIONS[]={
#define FILE_COPY(file_name){
(FP_ACTION)PSC_file_copy, (FP_ACTION) PSC_file_delete, file_name, NULL, NULL, txt_actions_copy_file, txt_actions_delete_file },
#define FILE_COPY_DIR(dir_name){
FP_ACTION)PSC_dir_copy, (FP_ACTION)PSC_dir_delete, dir_name, NULL, NULL, txt_actions_copy_dir, txt_actions_delete_dir },
#define FILE_COPY_NO_OVERWRITE(file_name){
(FP_ACTION)PSC_file_copy_no_overwrite, FP_ACTION)NULL, file_name, NULL, NULL , txt_actions_copy_no_over, txt_actions_skip_file },
#define FILE_COPY_SYS_NO_OVERWRITE(file_name) {
(FP_ACTION)PSC_file_copy_sys_no_overwrite, (FP_ACTION)NULL, file_name, NULL, NULL, txt_actions_copy_no_over, txt_actions_skip_file },
#define FILE_COPY_VER_CHECK(file_name){
(FP_ACTION)PSC_file_copy_ver_check, FP_ACTION)PSC_file_delete, file_name, NULL, NULL, txt_actions_copy_ver_check, txt_actions_delete_file },
#define FILE_COPY_SYSTEM(file_name){
(FP_ACTION)PSC_file_copy_system, (FP_ACTION)PSC_file_delete_system,(FP_ACTION)file_name, NULL, NULL, txt_actions_copy_system, txt_actions_delete_system },
#define FILE_COPY_WINDOWS(file_name){
(FP_ACTION)PSC_file_copy_windows, (FP_ACTION)PSC_file_delete_windows, file_name, NULL, NULL, txt_actions_copy_windows, txt_actions_delete_windows },
#define FILE_COPY_SHARE(file_name){
(FP_ACTION)PSC_copy_share,(FP_ACTION)PSC_delete_share, file_name, "Y", NULL, txt_actions_copy_share, txt_actions_delete_share },
#define FILE_COPY_SHARE_NO_DEL(file_name){
(FP_ACTION)PSC_copy_share,(FP_ACTION)PSC_delete_share,
file_name, "N", NULL, txt_actions_copy_share, txt_actions_skip_file },
#define FILE_COPY_SHARE_SYSTEM(file_name){
(FP_ACTION)PSC_copy_share_system,(FP_ACTION)PSC_delete_share_system, file_name, "Y", NULL, txt_actions_copy_share_system, txt_actions_delete_share_system },
#define FILE_COPY_SHARE_SYSTEM_NO_DEL(file_name){
(FP_ACTION)PSC_copy_share_system,(FP_ACTION)PSC_delete_share_system, file_name, "N", NULL, txt_actions_copy_share_system, txt_actions_skip_file },
#define FILE_COPY_SHARE_WINDOWS(file_name) {
(FP_ACTION)PSC_copy_share_windows,(FP_ACTION)PSC_delete_share_windows, file_name, "Y", NULL, txt_actions_copy_share_windows, txt_actions_delete_share_windows },
#define FILE_COPY_SHARE_WINDOWS_NO_DEL(file_name){
(FP_ACTION)PSC_copy_share_windows,(FP_ACTION)PSC_delete_share_windows, file_name, "N", NULL, txt_actions_copy_share_windows, txt_actions_skip_file },
#define REGISTRY_ADD_KEY(root,path, keyclass){
(FP_ACTION)PSC_registry_add_key,(FP_ACTION)PSC_registry_delete_key, root, path, keyclass, txt_actions_create_reg_key, txt_actions_delete_reg_key },
#define REGISTRY_ADD_VALUE(root,path,value){
(FP_ACTION)PSC_registry_add_value,(FP_ACTION)PSC_registry_delete_value, root, path, value, txt_actions_add_reg_value, txt_actions_delete_reg_value },
#define OS_SERVICE_ADD(service, d_name, binname) {
(FP_ACTION)PSC_install_os_service,(FP_ACTION)PSC_remove_os_service, service, d_name, binname, txt_actions_conf_osservice, txt_actions_remove_osservice },
#define TCP_SERVICE_ADD(service, port, proto){
(FP_ACTION)PSC_add_services_entry,(FP_ACTION)NULL, service, port, proto, txt_actions_conf_ipservice, txt_action_ipnodelete },
#define ODBC_DELETE_TRANSLATOR(translator){
(FP_ACTION)PSC_odbc_uninstall_translator,(FP_ACTION)NULL, translator, NULL, NULL, txt_actions_odbc_remove_trans, txt_actions_skip_file },
#define ODBC_DELETE_DRIVER(desc){
(FP_ACTION)PSC_odbc_uninstall_driver,(FP_ACTION)NULL, desc, NULL, NULL, txt_actions_odbc_rem_driver, txt_actions_skip_file },
#define ODBC_DELETE_CORE(comp){
(FP_ACTION)PSC_odbc_uninstall_core,(FP_ACTION)NULL, comp, NULL, NULL, txt_actions_odbc_remove_core, txt_actions_skip_file },
#define ODBC_INSTALL_CORE(comp, filelist){
(FP_ACTION)PSC_odbc_install_core,(FP_ACTION)NULL, comp, filelist, NULL, txt_actions_odbc_install_core, txt_actions_skip_file },
#define ODBC_INSTALL_DRV(desc, filelist){
(FP_ACTION)PSC_odbc_install_driver,(FP_ACTION)PSC_odbc_uninstall_driver, desc, filelist, NULL, txt_actions_install_driver, txt_actions_odbc_rem_driver },
#define ODBC_INSTALL_TRANSLATOR(translator, filelist){
(FP_ACTION)PSC_odbc_install_translator,(FP_ACTION)PSC_odbc_uninstall_translator, translator, filelist, NULL, txt_actions_install_trans, txt_actions_odbc_remove_trans },
#define END_ACTIONS {
NULL, NULL, NULL, NULL, NULL, 0L, 0L }};

We end up with a pseudo-defenition language. This allows us to create a header file for the engine which might look like:

#include "def_macros.h" /* header file containing the above struct's and macros */
#include "ibinstall.h" /* header file containing the external defines for the options */

DEFINE_GROUP(INTERBASE)
    MEMBER(IB_SERVER)
    MEMBER(IB_CLIENT)
    MEMBER(IB_CMD_TOOLS)
    MEMBER(IB_GUI_TOOLS)
    MEMBER(IB_DOC)
    MEMBER(IB_EXAMPLES)
    MEMBER(IB_DEV)
    MEMBER(IB_ODBC)
END_DEFINE_GROUP

DEFINE_GROUP(IB_CMD_TOOLS)
    MEMBER(IB_CMD_TOOLS_DB_MGMT)
    MEMBER(IB_CMD_TOOLS_USR_MGMT)
    MEMBER(IB_CMD_TOOLS_DB_QUERY)
END_DEFINE_GROUP

DEFINE_GROUP(IB_GUI_TOOLS)
    MEMBER(IB_GUI_TOOLS_DB_MGMT)
    MEMBER(IB_GUI_TOOLS_DB_QUERY)
END_DEFINE_GROUP

DEFINE_GROUP(IB_EXAMPLES)
    MEMBER(IB_EXAMPLE_API)
    MEMBER(IB_EXAMPLE_DB)
END_DEFINE_GROUP

DEFINE_DEPENDENCY(DEPEND_CAN_INSTALL_CLIENT)
    CHECK_NOT_CLASSIC
END_DEFINE_DEPENDENCY

DEFINE_DEPENDENCY(DEPEND_CAN_INSTALL_SERVER)
    CHECK_PRIV
    CHECK_NOT_CLASSIC
    CHECK_SERVER_NOT_RUNNING
END_DEFINE_DEPENDENCY

DEFINE_DEPENDENCY(DEPEND_IB_CLIENT)
    CHECK_NOT_CLASSIC
    CHECK_ELEMENT(IB_CLIENT)
END_DEFINE_DEPENDENCY

DEFINE_DEPENDENCY(DEPEND_IB_DEV_CLIENT)
    CHECK_ELEMENT(IB_CLIENT)
    CHECK_ELEMENT(IB_DEV)
END_DEFINE_DEPENDENCY

DEFINE_DEPENDENCY(DEPEND_IB_SERVER)
    CHECK_ELEMENT(IB_CLIENT)
END_DEFINE_DEPENDENCY

ACTIONS(IB_SERVER)
/* Common stuff necessary for setup to function on all platforms */
FILE_COPY_SYS_NO_OVERWRITE("setupapi.dll")
FILE_COPY_SYS_NO_OVERWRITE("cfgmgr32.dll")
FILE_COPY_SHARE_SYSTEM_NO_DEL("msvcrt.dll")
FILE_COPY_SHARE_SYSTEM_NO_DEL("odbccp32.dll")
FILE_COPY_SHARE_SYSTEM_NO_DEL("odbcint.dll")
FILE_COPY_VER_CHECK("ibinstall.dll")
FILE_COPY_VER_CHECK("ibuninst.exe")
/* Server actions */
FILE_COPY_SHARE_SYSTEM("gds32.dll")
FILE_COPY_SHARE("bin\\gdsintl.dll")
FILE_COPY("bin\\gstat.exe")
FILE_COPY_SHARE("bin\\ibguard.exe")
FILE_COPY_SHARE("bin\\iblicense.exe")
FILE_COPY_SHARE("bin\\iblicense.dll")
FILE_COPY("bin\\iblockpr.exe")
FILE_COPY_SHARE("bin\\ibserver.exe")
FILE_COPY("bin\\instreg.exe")
FILE_COPY("bin\\instsvc.exe")
FILE_COPY_SHARE("bin\\regcfg.dll")
FILE_COPY_VER_CHECK("bin\\regcfg.exe")

FILE_COPY("bin\\ibserver.cnt")
FILE_COPY("bin\\ibserver.hlp")

FILE_COPY("bin\\perform.cnt")
FILE_COPY("bin\\perform.hlp")

FILE_COPY_VER_CHECK("lib\\ib_udf.dll")

FILE_COPY_SHARE_SYSTEM_NO_DEL("mfc42.dll")

FILE_COPY("ibconfig")
FILE_COPY_SHARE("interbase.msg")
FILE_COPY_NO_OVERWRITE("isc4.gdb")
FILE_COPY_NO_OVERWRITE("isc4.gbk")
FILE_COPY("license.txt")
FILE_COPY("readme.txt")
FILE_COPY("Release_Notes.pdf")

REGISTRY_ADD_KEY("HKEY_LOCAL_MACHINE","SOFTWARE\\InterBase Corp", "REG_SZ")
REGISTRY_ADD_KEY("HKEY_LOCAL_MACHINE","SOFTWARE\\InterBase Corp\\InterBase", "REG_SZ")
REGISTRY_ADD_KEY("HKEY_LOCAL_MACHINE","SOFTWARE\\InterBase Corp\\InterBase\\CurrentVersion", "REG_SZ")
REGISTRY_ADD_VALUE("HKEY_LOCAL_MACHINE","SOFTWARE\\InterBase Corp\\InterBase\\CurrentVersion\\RootDirectory", ISC_INSTALL_DYN_VALUE"\\")
REGISTRY_ADD_VALUE("HKEY_LOCAL_MACHINE","SOFTWARE\\InterBase Corp\\InterBase\\CurrentVersion\\DefaultMode", "-r")
REGISTRY_ADD_VALUE("HKEY_LOCAL_MACHINE","SOFTWARE\\InterBase Corp\\InterBase\\CurrentVersion\\ServerDirectory",ISC_INSTALL_DYN_VALUE"\\bin\\")
REGISTRY_ADD_VALUE("HKEY_LOCAL_MACHINE","SOFTWARE\\InterBase Corp\\InterBase\\CurrentVersion\\Version","WI-V5.5.0")
REGISTRY_ADD_VALUE("HKEY_LOCAL_MACHINE","SOFTWARE\\InterBase Corp\\InterBase\\CurrentVersion\\GuardianOptions","1")
OS_SERVICE_ADD("InterBaseServer", "InterBase Server", "ibserver.exe")
OS_SERVICE_ADD("InterBaseGuardian", "InterBase Guardian", "ibguard.exe")
TCP_SERVICE_ADD("gds_db", "3050", "tcp")
END_ACTIONS

#include "undef_macros.h"/* header file which undefines the macros from def_macros.h */

This particular defenition file would be preprocessed into structures simular to shown below:

static const member INTERBASE_MEMBERS[]={
      IB_SERVER,
      IB_CLIENT,
      IB_CMD_TOOLS,
      IB_GUI_TOOLS,
      IB_DOC,
      IB_EXAMPLES,
      IB_DEV,
      IB_ODBC,
      0};

static const member IB_CMD_TOOLS_MEMBERS[]={
      IB_CMD_TOOLS_DB_MGMT,
      IB_CMD_TOOLS_USR_MGMT,
      IB_CMD_TOOLS_DB_QUERY,
      0};

static const member IB_GUI_TOOLS_MEMBERS[]={
      IB_GUI_TOOLS_DB_MGMT,
      IB_GUI_TOOLS_USR_MGMT,
      IB_GUI_TOOLS_DB_QUERY,
      0};

static const depend DEPEND_CAN_INSTALL_CLIENT[]={
      PSC_check_classic_server,0,NULL,0,
      NULL,0,NULL,0};

static const depend DEPEND_CAN_INSTALL_SERVER[]={
      PSC_check_classic_server,0,NULL,0,
      PSC_server_not_running,0,PSC_server_not_running,0,
      NULL,0,NULL,0};

static const depend DEPEND_IB_CLIENT[]={
      UTIL_check_dependency,IB_CLIENT,NULL,0,
      NULL,0,NULL,0};

static const depend DEPEND_IB_DEV_CLIENT[]={
      UTIL_check_dependency,IB_CLIENT,NULL,0,
      UTIL_check_dependency,IB_DEV,NULL,0,
      NULL,0,NULL,0};

static const COMPONENT components[]={
      INTERBASE,INTERBASE_MEMBERS,0,
      IB_SERVER,0,DEPEND_CAN_INSTALL_SERVER,
      IB_CLIENT,0,DEPEND_CAN_INSTALL_CLIENT,
      IB_CMD_TOOLS,IB_CMD_TOOLS_MEMBERS,0,
      IB_CMD_TOOLS_DB_MGMT,0,DEPEND_IB_CLIENT,
      IB_CMD_TOOLS_USR_MGMT,0,DEPEND_IB_CLIENT,
      IB_CMD_TOOLS_DB_QUERY,0,DEPEND_IB_CLIENT,
      IB_GUI_TOOLS,IB_GUI_TOOLS_MEMBERS,0,
      IB_GUI_TOOLS_DB_MGMT,0,DEPEND_IB_CLIENT,
      IB_GUI_TOOLS_USR_MGMT,0,DEPEND_IB_CLIENT,
      IB_GUI_TOOLS_DB_QUERY,0,DEPEND_IB_CLIENT,
      IB_DOC,0,DEPEND_IB_CLIENT,
      IB_EXAMPLES,0,DEPEND_IB_DEV_CLIENT,
      IB_DEV,0,DEPEND_IB_CLIENT,
      IB_ODBC,0,DEPEND_IB_CLIENT,
      0,0,0};

static const action IB_SERVER_ACTIONS[]={
PSC_file_copy_no_overwrite, PSC_file_delete,"isc4.gdb", NULL,NULL,1,
PSC_file_copy_no_overwrite, PSC_file_delete,"isc4.gbk", NULL,NULL,1,
PSC_file_copy, PSC_file_delete, "interbase.msg",NULL,NULL,1,
PSC_file_copy, PSC_file_delete, "ibconfig",NULL,NULL,1,
PSC_file_copy_ver_check, PSC_file_delete, "bin\\ibserver.exe",NULL,NULL,1,
PSC_file_copy, PSC_file_delete, "bin\\ibserver.hlp",NULL,NULL,1,
PSC_file_copy, PSC_file_delete, "bin\\ibserver.cnt",NULL,NULL,1,
PSC_file_copy_ver_check, PSC_file_delete, "bin\\ibguard.exe",NULL,NULL,1,
PSC_file_copy_ver_check, PSC_file_delete, "bin\\iblicense.exe",NULL,NULL,1,
PSC_file_copy_ver_check, PSC_file_delete, "bin\\iblicense.dll",NULL,NULL,1,
PSC_file_copy_ver_check, PSC_file_delete, "bin\\regcfg.exe",NULL,NULL,1,
PSC_file_copy_ver_check, PSC_file_delete, "bin\\regcfg.dll",NULL,NULL,1,
PSC_file_copy_ver_check, PSC_file_delete, "bin\\gdsintl.dll",NULL,NULL,1,
PSC_file_copy, PSC_file_delete, "bin\\gstat.exe",NULL,NULL,1,
PSC_file_copy, PSC_file_delete, "bin\\iblockpr.exe",NULL,NULL,1,
PSC_file_copy_ver_check, PSC_file_delete, "lib\\ib_udf.dll",NULL,NULL,1,
PSC_reg_add_key, PSC_reg_del_key, "HKEY_LOCAL_MACHINE","SOFTWARE\\InterBase Corp\\InterBase\\CurrentVersion\\",0,0,
PSC_reg_add_value, PSC_reg_del_value, "HKEY_LOCAL_MACHINE","SOFTWARE\\InterBase Corp\\InterBase\\CurrentVersion\\RootDir","",0,
PSC_reg_add_value, PSC_reg_del_value, "HKEY_LOCAL_MACHINE","SOFTWARE\\InterBase Corp\\InterBase\\CurrentVersion\\ServerDir","bin",0,
PSC_reg_add_value, PSC_reg_del_value, "HKEY_LOCAL_MACHINE","SOFTWARE\\InterBase Corp\\InterBase\\CurrentVersion\\Version","WI-V5.1.1",0,
NULL,NULL,NULL,NULL,NULL,0};

So, our install definition language is internally converted to static arrays of actions and dependencies, which can easily be parsed by our code. Note: we will have similar macros in the implementation to allow us to parse parse these arrays.

This design allows us to easily modify the actions takes by the install engine, without having to modify any of the basic algorithms. This is consistent with the original premise that this feature would be designed for use with other InterBase products such as InterClient.

Note: There would be once such definition file for every product on every platform, since this file defines the actions needed in order to install.

The only other internal data structures needed is a structure for the messages, and the structure of the uninstall file. Note, the messages the install API uses can not be stored in the interbase.msg file and accessed through the InterBase message mechanism. This is because when we are using the install engine, we are in the process of installing, or uninstalling, InterBase, therefore we can not be assured that the InterBase message file is present. Furthermore, since this feature is designed to be used to install other products, such as InterClient, we have no assurances that the messages file exists on the source path.

Lastly this feature is used to install, so we should make it self contained, thus alleviating the need to setup an environment in which the install program will function. Therefore, the messages should be stored within the install engine. This can be accomplished by defining a message structure, and initializing it in a header file. Note, this does make it more difficult to localize this feature, but if all messages are stored in one file, the amount of work needed to translate will be minimized.

The messages and message constants will be defined and initialized as follows:

#define ISC_INSTALL_ MAX_MESSAGE_LEN 300

typedef char     TEXTSTR[ISC_INSTALL_MAX_MESSAGE_LEN];
typedef TEXTSTR* PTEXTSTR;

Then we define as many text tables as necessary in the following manner.

static TEXTSTR error_messages[txt_error_last_string-txt_error_first_string+1]=
{
 "Warning message -n
     .
     .
     .
  "Warning message -1
 "Success                                       ",
 "This is error message 1                       ",
 "This is error message 2                       ",
 .
 .
 .
 "This is the last error message                "
};


typedef struct text_table
{

 STRING_ID   first_id;
 STRING_ID   last_id;
 STRING_ID   access_offset;  /* This is to be used to access the element in the array */
 TEXT*       tbl_description; /* This is primarely for use by makemsg                 */
 PTEXTSTR    list;           /* This is an array of text resources                    */

} STRTABLE, *PSTRTABLE;

Then define an array that will hold the information about the tables so we can obtain it quickly.

/* Define an array to acces each of the messages tables */
static const STRTABLE text_tables[TXT_NUM_OF_TABLES]=
{
 /* First of all goes error messages table */
 {
   txt_error_first_string,
   txt_error_last_string,
   txt_error_first_string,
   ERR_SECTION,
   error_messages
 },
 /* Now options array */
 {
     txt_options_first_string,
     txt_options_last_string,
     txt_options_first_string,
     OPT_SECTION,
     options_strings
 },
 /* Now actions text */
 {
     txt_actions_first_string,
     txt_actions_last_string,
     txt_actions_first_string,
     ACT_SECTION,
     actions_strings
  }
};
#define isc_install_success            0
#define isc_install_message1           1
#define isc_install_message2           2
.                                      .
.                                      .
.                                      .
#define isc_install_last_messsage      199
#define  isc_install_first_message     Warning message -n

In addition to this we will provide a format for the use to create a file called ibinstall.msg which would contain the translated messages. At runtime the install engine will look for this file when the setup program developer calls isc_install_load_external_file, read it into memory, and point msgs to it. If the file is not loaded, then msgs has already been initialized to point to english_msgs. This way, the install engine will always have the english messages, and if the developer wishes to provide the translated ones, the install engine will use those.

Detailed Algorithm

Each function will depend on several internal functions, the algorithm for which will be written at a later time. For now it is sufficient to say that these functions will be simple low level functions, and that they perform the action implied by their name. These functions are shown in bold for the sake of distinguishing them from other function calls.

The detailed algorithm for the eight API functions is as follows:

MSG_NO isc_install_set_option(OPTIONS_HANDLE* phandle, OPT option)

begin
    if (UTIL_check_handle_ref(phandle) OR UTIL_check_handle(*phandle)) then
        return (error invalid handle)
    if not (UTIL_valid_option(option)) then
        return (error invalid option)

    return UTIL_add_option(phandle, option)
end

MSG_NO isc_install_unset_option(OPTIONS_HANDLE* phandle,
                                OPT option)

begin
    if(UTIL_check_handle_ref(phandle) OR UTIL_check_handle(*phandle)) then
        return (error invalid handle)
   return UTIL_delete_option(phandle, option)
end

MSG_NO isc_install_clear_options(OPTIONS_HANDLE* phandle)

begin
     if (UTIL_check_handle_ref(phandle) OR UTIL_check_handle(*phandle)) then
        return (error invalid handle)
   return UTIL_free_options_list(phandle)
end

MSG_NO isc_install_precheck(OPTIONS_HANDLE handle,
                            char *source_path,
                            char *dest_path)

begin
        if (handle=null)
            return (error invalid handle);
    if (UTIL_check_handle(handle)) then
        return (warning invalid handle)
    if(PSC_check_system(handle)) then
        return (error invalid operating system)

        if(source_path not null OR source_path not empty)
        begin
               if( PSC_check_source(source_path))
                 return source_path error
       end

     if(dest_path not null)
     begin
         if(dest_path is empty string)
        begin
             if(PSC_suggest_destination(dest_path))
                 return error
             else
                 return success
       else  /* dest_path is not empty */
         begin
             if(PSC_check_destination(dest_path)
                 return dest_path error

              if(PSC_get_disk_free_space(pspace_avail))
                 return can not get free space

            if(UTIL_calaculate_diskspace(pspace_need))
                 return can not calculate space needed

          if(space_need > space_avail)
             return error not enough disk space
         end
    end

 return UTIL_check_dependency(handle)

 /***********************************************
  * UTIL_check_dependency will parse the dependency
  * tree specified in the defenition languag, and
  * call PSC_classic_not_found, if necessary.
  *
  * This is the contents of PSC_classic_not_found
  *
  * if GDS32.DLL does NOT have any version info,
  * then this is a classic server, rather
  * than a super client, thus abort.
  *
  * Check the Software\Borland\InterBase in the
  * registry for the RootDir, and use this for
  * the path to GDS32.DLL

 if (GetRegistryValue("HKEY_LOCAL_MACHINE",
                      "Software\Borland\InterBase",
                      "RootDir", &buff, length(buff)) then
     if not (GetVerInfo(buff+"\bin","GDS32.dll",...)) then
         return (error classic server found)

 * Check the Software\InterBase Corp.\InterBase in the
  * registry for the RootDir, and use this for
  * the path to GDS32.DLL *

 if (GetRegistryValue("HKEY_LOCAL_MACHINE",
                      "Software\InterBase Corp.\InterBase",
                      "RootDir", &buff, length(buff)) then
     if (NOT try_version(buff, "gds32.dll")) then
         return (error classic server found)

  * Check the windows system directory for GDS32.DLL

 if (NOT try_version(GetSystemDirectory(...),"GDS32.dll",...)) then
     return (error classic server found)

   *    end of PSC_classic_not_found()
  *********************************************************/

 end

MSG_NO isc_install_execute(OPTIONS_HANDLE handle,
                        TEXT *source_path,
                        TEXT *dest_path,
                        FP_STATUS *fp_status,
                        void *status_arg,
                        FP_ERROR *fp_error,
                        void *error_arg,
                        TEXT *uninstall_file_name)

ProcessError(err)
begin
   open(log)
   write(err)
     flush(log)
   close(log)
   return (fp_error (err) )
end

begin
 if(source_path is null OR source_path is empty OR dest_path is null
     OR dest_path is empty)
         return error path not valid
 if(dest_path not exist)
     if (UTIL_make_directory(dest_path)) then
          return (error can not create dest dir)
err=isc_install_precheck(handle, source_path, dest_path)
 if (err > 0)then
     log("error can not perform install error (err)")
     return (error can not perform install error  (err))

 /* Walk the list of options and create a queue of files
  * to be copied. Once this is done, commit the queue,
  * performing the actual copying of files and the creating
  * of any registry entries, as specified in the defenition
  * language file. This is done with
  * the use of the Windows Setup API. If any errors occur
  * during copying, process them using ProcessError. Also,
  * use fp_status to keep users appraised of the copying
  * status. Install the guardian service on WinNT or add the
  * Guardian to the Run line on Win95 */

log(copying files)
 fp_status(30)
 if not (UTIL_process_options(handle) then
     return (error could not process options)
 if not (UTIL_perform_actions(handle)) then
   begin
             UTIL_unperform_actions(handle)
     return (error could not copy files)
  end

 log(streaming options)
 do
     uninstall_file_name=UTIL_write_options(handle)
     if not (uninstall_file_name) then
         ret=ProcessError(error could not create uninstall file)
         if (ret=ABORT) then
             return (error could not create uninstall file)
 while (ret=RETRY)

 UTIL_move_text_log(UTIL_get_temp_dir()+ib_install.log,dest_path)
  UTIL_free_options_list(handle)
 return (success)
end

MSG_NO isc_uninstall_precheck(char *uninstall_file_name)
var
 handle;
end
begin
 if (UTIL_read_options(phandle, uninstall_file_name)) then
     return (error reading log file)

  if(PSC_check_system(handle))
         return (os not supported)

 if (UTIL_check_uninstall_dependencies(handle)) then
      return dependancy status;

UTIL_free_options_list(phandle)
 return (success)
end

MSG_NO isc_uninstall_execute(TEXT *uninstall_file_name,
                             FP_STATUS *fp_status,
                             void *status_arg,
                             FP_ERROR *fp_error,
                             void *error_arg)
var
 handle
end
ProcessError(err)
begin
 open(log)
 write(err)
 close(log)
 return (fp_error (err) )
end

begin
 err=isc_uninstall_precheck(uninstall_file_name)
 if (err > 0)then
      return (uninstall precheck error)

 if not (UTIL_read_options(phandle, uninst_file)) then
            return (error unable to read uninstall data)

PSC_check_system(handle)
/* We do not check the return value here, just fill out the handle */

 /* Walk the list of options and create a queue of files
  * to be uninstalled. Once this is done, commit the queue,
  * performing the actual uninstalling. This is done with
  * the use of the Windows Setup API. If any errors occur
  * during uninstallation, process them using ProcessError.  Also,
  * use fp_status to keep sers appraised of the copying
  * status. Uninstall the guardian.*/

 if not (UTIL_process_options(handle, list) then
     return (error could not process options)
 if not (UTIL_unperform_actions(handle, list)) then
     return (error could not uninstall files)

 UTIL_free_options_list(handle)
 return(success)
end

MSG_NO isc_install_get_message(handle,
                         MSG_NO msg_no,
                         char *msg,
                         int msg_len)

begin
     if(msg=null or msg_len=0)
         return invalid param
     if(UTIL_check_handle(handle))
         return invalid handle
   if (TXT_get_text(handle, msg_no, msg, msg_len)) then
     return (get message error)
  return (success)
end

MSG_NO isc_install_load_external_text(TEXT* external_path)
begin
   if(ibinstall.msg exist at external_path)
   then
     TXT_load_external_file()
 else
   return can not load the external file
 end
end

New/Affected modules

This feature will be implemented in a new component called INSTALL. This new component will contain the following modules:

New/Affected modules
Module Name Description
def_macros.h Contains pseudo language definitions.
undef_macros.h Undefs the pseudo language defines.
ibinstall.h This header file contains all the public, external defines, and data structures, along with the prototypes for interface.c. This is the file which a VAR will need to use to implement an install application using the InterBase Install API.
ibinstall.c The main module containing the entry points for the API. Each entry point implements the basic non platform specific algorithm, calls the generic utility functions from util.c for the lower level implementation details, and the platform specific functions from windows.c.
inst_common.h The main header file containing the data structures listed above in the Internal Data Structures section.
inst_text.c Contains default install error, warning messages, option names and escriptions, actions descriptions and messages that are logged out to the ib_install.log file. This module also contains the intrefaces provided for other modules to manipulate the text information. Those interfaces are also used by the MAKEMSG utility.
interbase.h This file contains the pseudo-defenition language which describes the groups of components, the components, and the actions to be taken for each component. This file will be included by util.c.
util_proto.h The function prototypes for the non platform specific utility functions.
util.c This module contains worker routines which will isolate the lower level details from the main code. For example there will be routines for adding, finding and removing nodes from the linked list of options, as well as non platform specific code for processing the options list, and reading the actions array from prod_description.h
util.h Contains static functions prototypes for UTIL and necessary typedefs and defines
psc_proto.h The function prototypes for the Platform Specific Code in windows.c for the time being. When this feature will be ported we will need to create a new .c file implementing the functions for the platform being ported.
psc_windows.c This module contains the windows specific implementation of some of the worker functions, such as the install_precheck and the install_execute functions. These will vary greatly from platform to platform, thus having an implementation for each one allows us to address the needs of each platform without cluttering the code with lots of #ifdef's.
psc_windows.h Contains static functions prototypes for PSC and necessary typedefs and defines

The contents of each of the prototypes are as follows:

ibinstall.h:

MSG_NO isc_install_set_option(OPTIONS_HANDLE* phandle, OPT option)
MSG_NO isc_install_unset_option(OPTIONS_HANDLE* phandle, OPT option)
MSG_NO isc_install_clear_options(OPTIONS_HANDLE* phandle)
MSG_NO isc_install_precheck(OPTIONS_HANDLE handle, char *source_path, char *dest_path)
MSG_NO isc_install_execute(OPTIONS_HANDLE handle, char *source_path, char *dest_path,
     FP_STATUS fp_status, void *status_arg, FP_ERROR fp_error, void *error_arg,
     char *uninst_file_name)
MSG_NO isc_uninstall_precheck(char *uninstall_file_name)
MSG_NO isc_uninstall_execute(char *uninstall_file_name, FP_STATUS fp_status,
     void *status_arg, FP_ERROR fp_error, void *error_arg)
MSG_NO isc_install_get_message(int msg_no, char *msg, int msg_len)
MSG_NO isc_install_get_info(OPT option, int info_type, void* info_buf, unsigned int buf_len)

util_proto.h:

MSG_NO UTIL_add_option
MSG_NO UTIL_calculate_diskspace
MSG_NO UTIL_check_dependance
MSG_NO UTIL_check_handle
MSG_NO UTIL_check_handle_ref
MSG_NO UTIL_delete_option
MSG_NO UTIL_find_option
MSG_NO UTIL_free_options_list
MSG_NO UTIL_make_directory
MSG_NO UTIL_move_text_log
MSG_NO UTIL_perform_actions
MSG_NO UTIL_process_options
MSG_NO UTIL_read_options
MSG_NO UTIL_unperform_actions
MSG_NO UTIL_valid_option
MSG_NO UTIL_write_options
void UTIL_log_message
void UTIL_log_action_text
MSG_NO UTIL_load_messages /* Currently not implemented */

psc_proto.h:

/* Functions required to inplement on all platforms shown only. Other functions may not be
   present. Some other functions may be present instead */

MSG_NO PSC_call_fp_error
MSG_NO PSC_call_fp_status
MSG_NO PSC_check_system
MSG_NO PSC_classic_not_found
MSG_NO PSC_check_destination
MSG_NO PSC_check_source
MSG_NO PSC_suggest_destination
MSG_NO PSC_get_free_diskspace
MSG_NO PSC_server_running
MSG_NO PSC_check_permissions(void)
MSG_NO PSC_get_temp_dir
MSG_NO PSC_move_file
MSG_NO PSC_get_text_string   /* Loads a test string from a file or resources */

inst_text_proto.h:

MSG_NO TXT_load_external_file
MSG_NO TXT_get_text
MSG_NO TXT_get_action_text
MSG_NO TXT_get_table_info

Testing Considerations

This feature is a new API, therefore in order to test it thoroughly each functions must be tested for boundary conditions, and invalid input, as well as testing all the functions in correct and incorrect order of execution.

MAKEMSG utility has an extra option in the dev build. Option -l <textfile> allows to load the ibinstall.msg file from the current directory and then to extract the loaded text out to the specified textfile. A test engineer can then diff the text file with a text file from which the ibinstall.msg file was created.