Main Page -> Papers -> Extending SMIT For Common Localized Tasks |
Extending SMIT For Common Localized Tasks
Introduction
In large Unix environments it is popular to extend the operating system with various forms of "local-ware" that provide a means for administrators to accomplish a set of common tasks with speed and consistency. One of the target audiences for these scripts and binaries is typically someone who is either more junior / operational or more familiar with another Unix. In either case the operator may not be intimately familiar with all the available locally written tools and options for those tools.
The creators of AIX have provided the SMIT interface as a simplified method to interact with less frequently used commands. SMIT provides a way for administrators not familiar with a command to validate input, prevent missing an important flag, or simply find the command they are looking for.
It is possible to extend SMIT to provide the same benefits for local-ware. In the case of locally written applications it may be of even more value as the scripts written in house are not part of the common Unix lexicon. The SMIT interface not only provides a helper for all command line options, but also provides an index / menu for the commands themselves.
SMIT is designed to provide many of the features required for administrative tasks such as logging, the option to log only (and capture commands for later execution), and ACL controls for individual SMIT screens.1 Why not extend that functionality to the tools written in house?
Writing A SMIT Application
The scope of this paper is not the content of the local-ware portion, but in constructing the SMIT interface to an existing local-ware repository, creating smit screens for 3rd party products, or just re-organizing some typically unrelated functionality to a common SMIT menu.
In this example I will create an alternate SMIT menu system for the fictional ACME Enterprises IT organization that has the need to provide a SMIT interface for a mixture of OS and locally written commands to both junior administrators (operations) as well as administrators of other Unices.
While this project is not really an application, it does have many of the version control, distribution, packaging, and debugging issues found in application development. I will need to synchronize our SMIT screens with the local-ware repository that ACME keeps in /opt/aeadmin/bin. The distribution method will need to be upgradeable without breaking existing SMIT objects or corrupting the ODM. Finally I want to reduce the need to document local-ware and simplify the process of what we do document.
Creating SMIT screens
Each SMIT screen is defined by one or more ODM objects. There are four types of SMIT-specific ODM objects that, when combined, define three different SMIT screens. The relationship is as follows:
Menus | sm_menu_opt | This object provides both menu items as well as alias capability for menus. Each menu item in a menu requires a sm_menu_opt object. The menu items are linked to a menu by a common id value and ordered by the value of the id_seq_num descriptor. The object that the menu item points to is determined by the values set for next_id and next_type. |
Dialog | sm_cmd_hdr | This object defines the dialog title, the command that will run, and the default values for the options (user entry items). It also determines some of the behaviors of the dialog itself such as warning the user before running a potentially dangerous command, or even displaying the dialog at all (and just running the command). The sm_cmd_hdr does not define the user inputs to the command but it does define the common id of the sm_cmd_opt objects that determine the parameters of each user input. |
sm_cmd_opt | This object defines each individual option that will be available in a dialog. A dialog can have 0 to many of these - typically one for each option the command has. These define the string that identifies the input, the data types / restrictions on input, and in the case of pick lists, the data to pick from. If the pick list is generated by a command then it is defined in this object. If the pick list is a simple list (a ring) then it is included in the object. | |
Selector | sm_name_hdr | This object defines a selector (type) dialog. A selector is effectively a dialog that does not run a command as the end result, but instead branches to either another selector screen or a dialog that will ultimately run the command. A selector has many of the same properties as a dialog in that it utilizes sm_cmd_opt objects to gather user input. The difference in respect to user input is that selector screens tend to be much simpler than dialogs. |
sm_cmd_opt | Defines each individual option that will be available in the selector screen. A selector will typically have only one of these but may have 0 if the selector screen is a ghost type (if the selector determines the best value on its own without user input). |
SMIT ODM objects used in this document are defined in the stanza / odmadd format and inserted into the ODM using the odmadd command. The examples I have provided are broken into individual "files" based upon their increasing complexity. There is no technical reason they could not be in a single file and added as a single group.
The odmadd stanza format begins with a header that consists with an ODM object type followed by a colon. The following lines consist of descriptor names and values assigned to those descriptors. In a database analogy, the table name is the ODM object type, the columns are the descriptors, and the rows are each ODM object inserted.
table_name: column1 = "RowX-Data1" column2 = "RowX-Data2" column3 = "RowX-Data3" |
Extending the SMIT Top Menu
The first task for this application is to create a task / application specific menu of commands as well as several entry points. The menu should be self-explanatory but the concept of multiple entry points may not be so obvious. The goal is to have the new menu accessible via the SMIT top menu (the one you see by default when starting SMIT), and two fast paths (aeadmin and ae).
The sm_menu_opt object defined in the following file (ODM Object 1) will provide the first requirement by modifying the "top" menu in the SMIT menu tree.
sm_menu_opt: id = "top_menu" id_seq_num = "210" next_id = "aeadmin" text = "ACME Administrative Actions" text_msg_file = "" text_msg_set = 0 text_msg_id = 0 next_type = "m" alias = "" help_msg_id = "" help_msg_loc = "aeadmin" help_msg_base = "" help_msg_book = "" |
The sm_menu_opt defined in ODM Object 1 will appear in the top menu because it shares an id with all other items in that menu. In this case the top menu items have an ID of "top_menu". The id_seq_num has been set to "210" to (sort) order its entry in the menu listing (near the bottom of the menu - because of the numbering scheme IBM has used for this menu).
The next_id descriptor is where this menu item will point to. It defines the SMIT screen (ODM object) to go to when this item is selected. I have specified that the next item is also a menu by setting a value of "m" in the next_type descriptor.
I set help_msg_loc to "aeadmin" so that I can later uniquely identify my SMIT ODM object from others. Technically this descriptor defines the string I pass to man to get help for this item but I am cheating by using this as a unique identifier to tell my items apart from IBM supplied items. (I will un-cheat later by creating and installing a common man page for this application.) The inclusion of this item will become more relevant later in this document when I discuss version control and deployment of the application.
The NLS (National Language Support)2 items text_msg_file, text_msg_set, and text_msg_id define where to get NLS items if I chose to internationalize this "application" I will not be using these items because the scope of this modification is only for the local IT group and does not require internationalization.
In the screenshot, labeled SMIT Screen 1, is the resulting SMIT menu from running smitty (or smit in a text environment3). As you can see, my new menu item showed up third from the bottom in the menu list.
System Management Move cursor to desired item and press Enter. Software Installation and Maintenance Software License Management Devices System Storage Management (Physical & Logical Storage) Security & Users Communications Applications and Services Print Spooling Advanced Accounting Problem Determination Performance & Resource Scheduling System Environments Processes & Subsystems Applications Installation Assistant ACME Administrative Actions Cluster Systems Management Using SMIT (information only) F1=Help F2=Refresh F3=Cancel F8=Image F9=Shell F10=Exit Enter=Do |
The ACME Administration Menu
The next task is to define the menu items and additional fast paths to the proposed ACME Administration (aeadmin) menu. If I were to choose the ACME Admin menu now, having only created the menu pointer, SMIT would produce an error because the actual menu has not been created. In the next ODM definitions listed in ODM Object 2 I first define a single menu item for the menu to point to an existing SMIT fast path. Here the menu item is the same as before except I am defining the object with a id descriptor of the menu.
Additionally, the second object in the ODM Object 2 listing creates an additional alias that can be used as a fast path to the menu. Here I associate the ae string with the aeadmin menu.
I do not need to define the name of the menu screen as that information is derived from the pointer item in the previous menu as well as the text set in the alias object.4
sm_menu_opt: id_seq_num = "010" id = "aeadmin" next_id = "lvm" text = "Logical Volume Manager" text_msg_file = "" text_msg_set = 0 text_msg_id = 0 next_type = "m" alias = "" help_msg_id = "" help_msg_loc = "aeadmin" help_msg_base = "" help_msg_book = "" sm_menu_opt: id_seq_num = "" id = "ae" next_id = "aeadmin" text = "ACME Administrative Actions" text_msg_file = "" text_msg_set = 0 text_msg_id = 0 next_type = "m" alias = "y" help_msg_id = "" help_msg_loc = "aeadmin" help_msg_base = "" help_msg_book = "" |
The first sm_menu_opt object should look much like the one I defined in ODM Object 1 with a few exceptions. One key difference is that I am now in a new menu so the id matches all other object IDs that belong to that menu. The next key difference is that the next_id points to an object that is an already defined SMIT (menu) object for the Logical Volume Manager. Note that the lvm object exists at this point so this menu is a working submenu item.
The second sm_menu_opt object is an alias for a SMIT fast path. Here I am defining an additional fast path to get directly to this new menu. I set the id descriptor to the preferred alias (fast path) name. I set the next_id and text descriptors to point to the menu. Users can use the aeadmin fast path (same as the id descriptor) or now use just ae.
As with all our modifications, these objects do not use NLS support, and set help_msg_loc to my unique standard string "aeadmin" that will allow me to locate all my objects later.
The resulting screen, seen in the SMIT Screen 2 screen capture, is now available from the SMIT top menu link, the aeadmin and ae fast paths.
ACME Administrative Actions Move cursor to desired item and press Enter. Logical Volume Manager F1=Help F2=Refresh F3=Cancel F8=Image F9=Shell F10=Exit Enter=Do |
The choice of Logical Volume Manager as a menu item demonstrates that I could create a custom SMIT menu of only existing items using known fast paths. This is not as effective as creating my own content, so I will begin to insert local-ware screens in the next section.
Creating A Command / Dialog
In order to front a command I will need to utilize at least one sm_cmd_hdr / Dialog object, even if the user does not interact with it.5 In this example we will suspend belief for a moment and pretend that the local IT staff wrote the ls command, and that I need to write a SMIT dialog for it.
The basic steps for this task will be to first create a new menu item in the aeadmin menu, then create a dialog screen that will accept user input. The dialog screen needs to provide a relevant default value and validate the user input based upon requirements that we define.
To create this SMIT dialog I will need to define at least one of each of the following: sm_menu_opt, sm_cmd_hdr, and sm_cmd_opt. These objects are defined in the ODM Object 3 listing that follows.
sm_menu_opt: id_seq_num = "020" id = "aeadmin" next_id = "ae_ls" text = "Print ls results" text_msg_file = "" text_msg_set = 0 text_msg_id = 0 next_type = "d" alias = "" help_msg_id = "" help_msg_loc = "aeadmin" help_msg_base = "" help_msg_book = "" sm_cmd_opt: id_seq_num = "10" id = "ae_ls_opts" disc_field_name = "ls_target_dir" name = "Directory to list" name_msg_file = "" name_msg_set = 0 name_msg_id = 0 op_type = "" entry_type = "f" entry_size = 0 required = "+" prefix = "" cmd_to_list_mode = "" cmd_to_list = "" cmd_to_list_postfix = "" multi_select = "" value_index = 0 disp_values = "" aix_values = "" values_msg_file = "" values_msg_set = 0 values_msg_id = 0 help_msg_id = "" help_msg_loc = "aeadmin" help_msg_base = "" help_msg_book = "" sm_cmd_hdr: id = "ae_ls" option_id = "ae_ls_opts" has_name_select = "" name = "ls of a directory" name_msg_file = "" name_msg_set = 0 name_msg_id = 0 cmd_to_exec = "ae_ls_proc () \n\ {\n\ DIR=$1\n\ /usr/bin/ls \"${DIR}\" \n\ }\n\ bb_ls_proc " ask = "y" exec_mode = "n" ghost = "" cmd_to_discover = "/usr/bin/echo '#ls_target_dir'\n\ /usr/bin/pwd" cmd_to_discover_postfix = "" name_size = 0 value_size = 0 help_msg_id = "" help_msg_loc = "aeadmin" help_msg_base = "" help_msg_book = "" |
In the sm_menu_opt object, defined in the ODM Object 3 listing, I define another menu item in our aeadmin menu. I set the id_seq_num as "020" to make it appear (sort) after the previously created item that I set to "010". I use a BASIC-like numbering scheme that will allow me to insert additional menu items later without the need to re-number the entire menu to accommodate a new item. The key difference with this menu item is that points to an entirely new object we call ae_ls that is next_type of "d". So I am telling SMIT that the object this points to is a dialog and not another menu. Otherwise, this sm_menu_opt should look much like menu items I created in the previous examples.
The sm_cmd_hdr object defines the dialog that I will be using. In itself, it only describes part of the dialog. The sm_cmd_opt defines each piece of data that the user will enter. The combined sm_cmd_opt's define the bulk of the user interactive components in the dialog. As well as providing the common binding framework, the sm_cmd_hdr provides the actual content of the command that will run as well as the (optional) default values. Together, these two objects define the entire SMIT dialog experience.
I will look at the sm_cmd_hdr object first6 as it is the framework that the sm_cmd_opt objects hang on. The id descriptor is set to the same name as the next_id in our previously created menu item. I define an option_id descriptor so that all the options can be placed, in order, into this dialog. SMIT will look for all sm_cmd_opt objects defined in the ODM with an id that matches this value. The name descriptor is set to the title I wish for the top of the dialog. This dialog does not have a pre-selector screen that would determine what group of options to present to the user so we do not set the has_name_select descriptor. (The selector screen and the sm_name_hdr object will be introduced in the next example.)
The command and the default values are also defined in the sm_cmd_hdr object through the cmd_to_exec and the cmd_to_discover descriptors. The cmd_to_exec example here is intentionally overly complex to demonstrate writing multiple line code in the ODM. Ideally, complex code is best maintained as a separate script (in a directory such as /opt/aeadmin/bin). This is further necessitated by the fact that this field has a limit of 1024 characters. The options I define in sm_cmd_opt objects will be passed as parameters to this command when it runs. The cmd_to_discover descriptor is used to define default values for the options. The first line of the output prints the disc_field_name of the sm_cmd_opt object, while the second line prints the desired default value. Multiple defaults can be specified as colon separated values. (In the next example I will show creating multiple defaults and what happens if some defaults are set, while others are not.)
Some relevant but less obvious sm_cmd_hdr descriptors I have set here as well. The value for ask will tell SMIT to warn the user before running the command and ask if they are sure the command should run. The default value for ask is "n", the alternative we chose is "y", so SMIT will treat the locally written version of ls as a potentially destructive command. The exec_mode descriptor defines how the output is handled in the result screen. By default, the output is piped to SMIT and scrolls as it runs. The value for ghost determines if the dialog (prompting for input) should be displayed. In this case I want the dialog to display because I expect user input, and do not rely upon default values. If I had a command / dialog that displayed status of a service, the current time, or something that does not require user input prior to running then I could set this to "y" instead of the default "n". Finally, I set my help_msg_loc value as it is available in all four SMIT objects.
Each user input is defined by an individual sm_cmd_opt object. In this example I only set one (for the directory to run ls against). If I had multiple inputs they would share the same id descriptor. The id descriptor of all the sm_cmd_opt objects associated with a dialog should match the option_id that is set in the sm_cmd_hdr object that defines the dialog. Here, I set them to "ae_ls_opts"7. Like menu items, the id_seq_num descriptor determines the order that the option will show up in the dialog. Also like menu objects is the name descriptor that defines what the prompt should be for the user input field. The programatic handle for the object is the name set in disc_field_name. This value is referred from the cmd_to_discover descriptor set earlier in the sm_cmd_hdr object should I choose to set a default value.
User inputted data is returned from each sm_cmd_opt object to the sm_cmd_hdr object's cmd_to_exec as ordered parameters to the command. The order will be the same as they appeared in the dialog. Any prefixes, such as a flag, is specified with the prefix descriptor. Even though they are ordered, best practices in command line utility development suggests that command line parameters should be acceptable in any order. Should the order of parameters be necessary, and the order of the dialog out of order then the items can be re-ordered using a wrapper function in the cmd_to_exec descriptor of the sm_cmd_hdr object (as in the sm_cmd_hdr example in ODM Object 3).
Many of the remaining sm_cmd_opt object descriptors define the nature and type of user input such as if they are required, they are a list, null values are allowed, and how to return the data. In this example I will only list the deviations from the defaults. More examples will be given later and complete documentation for the objects and descriptors is available from the AIX Information Center.
The op_type descriptor is set to the default so this is a simple (non-list) input. I have set the entry_type to "f" for a file (or directory). The size of the input is not (artificially) limited as entry_size is set to 0. The value for required is set to "+" that will require the user to input some data or the command cannot run. This descriptor also defines what is sent to the command if the user does not enter data.
ls of a directory Type or select values in entry fields. Press Enter AFTER making all desired changes. [Entry Fields] * Directory to list [/proj/smitscr] / F1=Help F2=Refresh F3=Cancel F4=List F5=Reset F6=Command F7=Edit F8=Image F9=Shell F10=Exit Enter=Do |
The resulting dialog, shown in SMIT Screen 3, displays the single command option (the directory) with the default (the current working directory). The "*" character on the left notifies the user that this is a required value and the "/" on the far right notifies them that the input is a file / directory. When the enter key is pressed from this dialog the user will be prompted to insure that they want to run the command (as it may be dangerous / destructive) and then they will see the ls results of this directory.
Not shown here is the additional menu item that will be added to the aeadmin menu. This is menu item addition, along with the an item introduced in the next example, is visible later in the SMIT Screen 4 screenshot.
Creating A More Complex Command / Dialog
The final example creates a more complex dialog with a preceding selector screen. This is for a fictitious script that takes different parameters depending upon a "high" or "low" selection. Concepts like high or low might be appropriate, for example, for a command that can either read or write. When reading it might ask for query-specific or result limiting data and if it writes it would ask for content to write. The initial selector screen determines our intent with this command, and then directs us to the most appropriate input dialog for our proposed action.
Note that I have left some additional comments in the object definitions to help the reader to decode and differentiate the contents. The comments are anything following the "*" character. The section "division" lines were left in the sample code because of the large size of the information. odmadd will ignore this content.
* -------------------------------------------------------- * ACME Enterprises Admin Menu Item sm_menu_opt: id = "aeadmin" id_seq_num = "015" next_id = "ae_pco_sel" text = "Process Config Options" text_msg_file = "" text_msg_set = 0 text_msg_id = 0 next_type = "n" alias = "" help_msg_id = "" help_msg_loc = "aeadmin" help_msg_base = "" help_msg_book = "" * -------------------------------------------------------- * Selector screen sm_name_hdr: id = "ae_pco_sel" next_id = "ae_pco_" option_id = "ae_pco_opts" has_name_select = "" * Can be fast path name = "Config High or Low" name_msg_file = "" name_msg_set = 0 name_msg_id = 0 type = "r" * Raw (just append user input) ghost = "" * Must display cmd_to_classify = "" cmd_to_classify_postfix = "" raw_field_name = "" cooked_field_name = "" next_type = "d" * Dialog is next help_msg_id = "" help_msg_loc = "aeadmin" help_msg_base = "" help_msg_book = "" * -------------------------------------------------------- * Options for selector screen sm_cmd_opt: id = "ae_pco_opts" id_seq_num = "0" disc_field_name = "" name = "High or Low dialog" name_msg_file = "" name_msg_set = 0 name_msg_id = 0 op_type = "r" * Ring entry_type = "t" * Text entry_size = 4 * Up to 4 chars in High|Low required = "+" * Always send data back prefix = "" cmd_to_list_mode = "a" * Get all fields ("" == "a") cmd_to_list = "" cmd_to_list_postfix = "" multi_select = "" * ("" = no) value_index = 0 * Defaults to High disp_values = "High,Low" values_msg_file = "" values_msg_set = 0 values_msg_id = 0 aix_values = "high,low" help_msg_id = "" help_msg_loc = "aeadmin" help_msg_base = "" help_msg_book = "" * -------------------------------------------------------- * First Dialog (low) sm_cmd_hdr: id = "ae_pco_low" option_id = "ae_pco_low_opts" has_name_select = "" * Can be fast path name = "Config Low" name_msg_file = "" name_msg_set = 0 name_msg_id = 0 cmd_to_exec = "echo LOW " ask = "" exec_mode = "" ghost = "" * Must display cmd_to_discover = "" cmd_to_discover_postfix = "" name_size = 0 value_size = 0 help_msg_id = "" help_msg_loc = "" help_msg_loc = "aeadmin" help_msg_base = "" help_msg_book = "" * -------------------------------------------------------- * Second Dialog (high) sm_cmd_hdr: id = "ae_pco_high" option_id = "ae_pco_high_opts" has_name_select = "" * Can be fast path name = "Config High" name_msg_file = "" name_msg_set = 0 name_msg_id = 0 cmd_to_exec = "echo HIGH " ask = "" exec_mode = "" ghost = "" * Must display cmd_to_discover = "echo '#opt_one:opt_three'\n\ echo '768:Optional'" cmd_to_discover_postfix = "" name_size = 0 value_size = 0 help_msg_id = "" help_msg_loc = "" help_msg_loc = "aeadmin" help_msg_base = "" help_msg_book = "" * -------------------------------------------------------- * Option 1 for low dialog * [Pick y|n get a flag or not] sm_cmd_opt: id = "ae_pco_low_opts" id_seq_num = "10" disc_field_name = "opt_one" name = "Pick Y/N for -a flag" name_msg_file = "" name_msg_set = 0 name_msg_id = 0 op_type = "r" * Ring entry_type = "t" * Text entry_size = 3 * Up to 3 chars in Yes|No required = "+" * Always send back data prefix = "" cmd_to_list_mode = "a" * Get all fields ("" == "a") cmd_to_list = "" cmd_to_list_postfix = "" multi_select = "" * ("" = no) value_index = 1 * Defaults to No disp_values = "Yes,No" values_msg_file = "" values_msg_set = 0 values_msg_id = 0 aix_values = "-a," help_msg_id = "" help_msg_loc = "aeadmin" help_msg_base = "" help_msg_book = "" * -------------------------------------------------------- * Option 2 for low dialog * [Pick a number from a list F4-style, pre-pend a flag] sm_cmd_opt: id = "ae_pco_low_opts" id_seq_num = "20" disc_field_name = "opt_two" name = "Pick from short list" name_msg_file = "" name_msg_set = 0 name_msg_id = 0 op_type = "l" * List entry_type = "#" * Numeric entry_size = 1 * Only one char in number required = "+" * Always send back data prefix = "-b " cmd_to_list_mode = "a" * Get all fields ("" == "a") cmd_to_list = "for NUM in 1 2 3 4 5 6 7 8 9\n\ do\n\ echo ${NUM}\n\ done" cmd_to_list_postfix = "" multi_select = "" * ("" == no) value_index = 0 disp_values = "" values_msg_file = "" values_msg_set = 0 values_msg_id = 0 aix_values = "" help_msg_id = "" help_msg_loc = "aeadmin" help_msg_base = "" help_msg_book = "" * -------------------------------------------------------- * Option 3 for low dialog * [Pick file(s) from /etc, nulls ok, no flag pre-pend] sm_cmd_opt: id = "ae_pco_low_opts" id_seq_num = "30" disc_field_name = "opt_three" name = "Pick file from list" name_msg_file = "" name_msg_set = 0 name_msg_id = 0 op_type = "l" * List entry_type = "f" * File entry_size = 0 * Maximum size allowed required = "?" * Do not send if empty prefix = "" cmd_to_list_mode = "a" * Get all fields ("" == "a") cmd_to_list = "ls /etc" cmd_to_list_postfix = "" multi_select = "y" value_index = 0 disp_values = "" values_msg_file = "" values_msg_set = 0 values_msg_id = 0 aix_values = "" help_msg_id = "" help_msg_loc = "aeadmin" help_msg_base = "" help_msg_book = "" * -------------------------------------------------------- * Option 1 for high dialog * [Enter a number, required, prepend a flag] sm_cmd_opt: id = "ae_pco_high_opts" id_seq_num = "10" disc_field_name = "opt_one" name = "Enter a number" name_msg_file = "" name_msg_set = 0 name_msg_id = 0 op_type = "" * Normal (!ring, !list) entry_type = "#" * Number entry_size = 9 * 1 Billion possibilites (negative?) required = "+" * Must enter data prefix = "-x " cmd_to_list_mode = "" cmd_to_list = "" cmd_to_list_postfix = "" multi_select = "" value_index = 0 disp_values = "" values_msg_file = "" values_msg_set = 0 values_msg_id = 0 aix_values = "" help_msg_id = "" help_msg_loc = "aeadmin" help_msg_base = "" help_msg_book = "" * -------------------------------------------------------- * Option 2 for high dialog * [Pick from a large list, required, pre-pend flag] sm_cmd_opt: id = "ae_pco_high_opts" id_seq_num = "20" disc_field_name = "opt_two" name = "Pick from large list" name_msg_file = "" name_msg_set = 0 name_msg_id = 0 op_type = "l" entry_type = "#" * Number entry_size = 2 required = "+" * Must enter data prefix = "-y " cmd_to_list_mode = "a" cmd_to_list = "for X in 0 1 2 3 4 5 6 7 8 9\n\ do for Y in 0 1 2 3 4 5 6 7 8 9\n\ do\n\ echo $(( $(( ${X} * 10 )) + ${Y} ))\n\ done\n\ done" cmd_to_list_postfix = "" multi_select = "" value_index = 0 disp_values = "" values_msg_file = "" values_msg_set = 0 values_msg_id = 0 aix_values = "" help_msg_id = "" help_msg_loc = "aeadmin" help_msg_base = "" help_msg_book = "" * -------------------------------------------------------- * Option 3 for high dialog * [Enter optional text, flag sent conditionally] sm_cmd_opt: id = "ae_pco_high_opts" id_seq_num = "30" disc_field_name = "opt_three" name = "Enter some text (optionally)" name_msg_file = "" name_msg_set = 0 name_msg_id = 0 op_type = "" entry_type = "t" * Text entry_size = 10 * Not so large required = "" * Only sent if user changes prefix = "-z " cmd_to_list_mode = "" cmd_to_list = "" cmd_to_list_postfix = "" multi_select = "" value_index = 0 disp_values = "" values_msg_file = "" values_msg_set = 0 values_msg_id = 0 aix_values = "" help_msg_id = "" help_msg_loc = "aeadmin" help_msg_base = "" help_msg_book = "" |
I begin with a sm_menu_opt object that defines a menu item in the (aeadmin) application menu. The difference with this sm_menu_opt is that the next_type descriptor points to a selector / sm_name_hdr object, identified by "n", rather than another menu or dialog as in the previous examples. The selector object id is specified in the next_id descriptor as "ae_pco_sel".
The next object is a sm_name_hdr that defines the selector screen. This is a simple selector that does not cascade into another selector so I specify that the next_type is a dialog. The selector item is like a dialog in that I use sm_cmd_opt objects to define the options in the selector screen. They are linked, like the previous dialog object to the sm_cmd_opt object(s) via the option_id defined in the selector object and the id defined in the sm_cmd_opt(s). They both share the "ae_pco_opts" value for these descriptors.
The next_id in the sm_name_hdr is incomplete ("ae_pco_"). It will be appended with one of the items from the aix_values defined in, and returned from, the sm_cmd_opt objects. The concatenation of these two items point to what dialog will be displayed next.
The sm_cmd_opt for the selector screen defines a simple ring (multiple choice - pick one) option with the values of "High" and "Low". The aix_values list defines what will actually be sent back to the selector screen / object for each item in the disp_values ring. There should be a one-to-one ordered relationship between disp_values and aix_values. (Note that in the Option 1 - low dialog I break this rule by sending back a switch string for the "Yes" option and a null value for the "No" option.)
The next two items defined are the two (high and low) dialogs. As in the previous example, these are defined by sm_cmd_hdr objects. In comparison to the previous dialog example both of these dialogs are quite simple. The "High" dialog has a defined cmd_to_discover descriptor that will set defaults for two of the three options. In this example the first and third options are set to a default leaving the second option without. The resulting dialog screenshot, shown in SMIT Screen 6, shows the resulting values set and the second option as blank.
I have set the cmd_to_exec in both dialogs to run the echo command for the illustrative purpose of simply echoing the parameters as passed to the command.
Each of the options from the example in ODM Object 4 are broken out in the following table.
Option 1 — low | This is a "ring" of two possible values. The dialog choices are Yes and No but the returned value (parameter to the command) is either a -a flag or not. This is a boolean choice that turns a flag on or not. A ring is a pre-defined, comma separated, list of values that are either tab or F4 selectable. |
Option 2 — low | Pick from a list of numbers (1 to 9). Return the chosen value with a -b switch prepended to it. The list is generated with a simple for loop that prints one list value per line. Input is required for the dialog to execute the command. |
Option 3 — low | Pick zero, one, or more files from a list generated from all the files in the /etc directory. The input size is not limited (other than the maximum, typically 1024). If more than one file is chosen values are sent back as a space separated list of those files. |
Option 1 — high | Enter a required free-form number. Prepend a -x flag before the value. Because this is a numeric type only numbers can be entered. The number can begin with a "-" or "+" sign. |
Option 2 — high | Pick a required number from a list of 0 to 99. Prepend a -y flag before the value. The cmd_to_list runs as korn shell command. |
Option 3 — high | Enter some optional, free-form, text. Send the data with a prepended -z flag only if user enters/changes default values. |
ACME Administrative Actions Move cursor to desired item and press Enter. Logical Volume Manager Process Config Options Print ls results F1=Help F2=Refresh F3=Cancel F8=Image F9=Shell F10=Exit Enter=Do |
The menu in screenshot SMIT Screen 4 is the result of all the sm_menu_opt additions to the ACME Administration (aeadmin) menu. The last menu item created in the ODM Object 4 called "Process Config Options" appears second in the menu because of the value set in the id_seq_num descriptor in the sm_menu_opt object.
Config High or Low Type or select a value for the entry field. Press Enter AFTER making all desired changes. [Entry Fields] * High or Low dialog [High] + F1=Help F2=Refresh F3=Cancel F4=List F5=Reset F6=Command F7=Edit F8=Image F9=Shell F10=Exit Enter=Do |
The SMIT Screen 5 screenshot is the selector screen that determines which dialog will be displayed next. The single question displayed is the "High or Low dialog" prompt defined in the sm_cmd_opt object along with the default "High" value selected from the choice ring defined in the same object. This is a required (*) value that can be picked from a list (+).
Options to the selector screen are "High" and "Low" as defined in the sm_cmd_opt object descriptor disp_values, but the value returned is either "high" or "low" as defined in the aix_values descriptor. The example here is only a matter of difference in case between the choices and the returned values, but the point is that an entirely different, but relative, value can be returned than what the user chooses.
Config High Type or select values in entry fields. Press Enter AFTER making all desired changes. [Entry Fields] * Enter a number [768] # * Pick from large list [] +# Enter some text (optionally) [Optional] F1=Help F2=Refresh F3=Cancel F4=List F5=Reset F6=Command F7=Edit F8=Image F9=Shell F10=Exit Enter=Do |
The screenshot shown in SMIT Screen 6 shows the options for the "High" Dialog as well as the default values defined in the cmd_to_discover descriptor in the sm_cmd_hdr object for this dialog. Note that the middle value is not set to a default. When the defaults are set by name it is permissible to only set some and order is not required as the defaults are set by the disc_field_name of the sm_cmd_opt and not the order they appear in the dialog.
Config Low Type or select values in entry fields. Press Enter AFTER making all desired changes. [Entry Fields] * Pick Y/N for -a flag [No] + * Pick from short list [] +# Pick file from list [] +/ F1=Help F2=Refresh F3=Cancel F4=List F5=Reset F6=Command F7=Edit F8=Image F9=Shell F10=Exit Enter=Do |
The "Low" dialog shown in the SMIT Screen 7 screenshot shows the options with two that are required (*), all three are selectable via the F4 key (+), one is a numeric entry (#), and one is a file or directory (/). Even though all three are selectable via the F4 key, each is a different type. The first item is a ring of values, the second is a list of numbers and the third is a list generated by the ls command.
Bits And Pieces
Inserting ODM Objects
In the actual ACME Enterprises deployment8, ODM objects were grouped into actions that were placed into files. Each file held all ODM objects for the menu item, dialog, and options. This was done to ease development by associating each addition of SMIT functionality with a single file. This was much easier than manipulating one giant file with all the objects. As a result syntax errors returned from the odmadd command were easier to locate in the object descriptions.
I then wrote a series of scripts to check and insert the objects. The check was primarily for the help_msg_loc descriptor to insure that it was set for every ODM object that was created. These (ODM object source) files were placed into a common directory where the check and add scripts can find them. These types of management utilities are not required but make for easier development and debugging of the objects you create.
When inserting objects into the ODM it is advisable to test the objects outside of the ODM prior to modifying the actual / production ODM database. This can be done by using an alternate copy of the SMIT specific ODM objects. The ODMDIR environmental variable for odmadd and the -o option for smit / smitty allow these alternate objects to be used rather than the actual ODM. This process is defined in the "Adding Tasks to the SMIT Database" Infocenter page.
Version Control
The method of version control used by ACME Enterprises was to create a menu item off the aeadmin screen that contained the version string as a text parameter. This allows someone to view the current version of the application from both within the ODM (via a call to odmget) as well as from the SMIT interface on the aeadmin menu. The version information is tied closely to the actual objects rather than kept in a separate version file where it could grow out of sync with the installed ODM objects.
One other complexity of version control is insuring that all local-ware scripts that the SMIT dialogs rely upon actually exist in the local script directory. A similar form of version control is recommended for these scripts so that they are kept in sync with the ODM objects. The loosely coupled nature of the command line API allows for some flexibility in this regard. ODM dialogs should not rely upon positional parameters in scripts - meaning: your script should accept parameters in any order, and should validate the parameters it receives. Any input errors should cause the script to exit with a non-zero value. SMIT will give ordered input to the commands, but this should not be assumed as the scripts may be called outside of SMIT.
Upgrades
The safest method I found to update the ODM objects was to remove all tagged objects and replace them with the new version of the tagged objects. This tag was mentioned earlier in the document as the help_msg_loc descriptor that is common to every SMIT ODM object. An odmdelete can be keyed on this descriptor that will delete only SMIT objects related to this key, and therefore the application.
Selectively deleting all application objects and then re-inserting the new / upgraded ones is much easier than maintaining some sort of ODM diff. This allows me to jump from one version to another without the need to modify ODM objects in place.
If the application is packaged as a installp package then the key method described here should not be necessary. Creating installp packages is beyond the scope of this document, but can make upgrades and version control much easier. The uninstall portion of the package can know what ODM objects to remove, solving the versioning of the ODM objects. The local-ware can be included as part (or dependency) of the package, solving issues with relationships and dependencies between the SMIT screens and the capabilities of the binaries / scripts that they call.
Footnotes
1. | SMIT can log both access (smit.log) as well as commands run (smit.script). This functionality is roughly equivalent to that of the shell history file but is actually a superset of shell features. Additionally, access to individual smit screens can be controlled through access control lists defined in smitacl.user and smitacl.group. Additional information on SMIT can be found on the smit(ty) man page or the AIX Information Center. |
2. | The man page for gencat is a good place to start when investigating NLS support and how to create a .cat file. |
3. | smit (the X-Window version) and smitty (curses based text) both use the same ODM objects. So if you are one of the few people that use the X version you will see the same items, but in the X interface to SMIT. |
4. | This is evident when you watch how SMIT walks the menus as you start with a fast path. One example of this is the tcpip fast path, it is several menus in from the top_menu. When you run smit -t tcpip you can see (in the smit.log file) how SMIT loads the previous commo menu before it loads the actual tcpip menu. |
5. | A dialog or selector screen that does not require user input (it gets the info it needs on its own) and is not seen by the user is a "ghost" dialog. The ghost descriptor in these objects is for this purpose. This makes a menu item to appear to run a command directly, when in reality, at least one dialog is operating under the covers. |
6. | The order here is for purposes of discussion. The order they are specified as objects in our example files are more for design clarity and application relatedness. The actual order these objects are entered do not matter as they are un-ordered in the ODM, and are pulled from the ODM based upon type, not the order they were created / entered. |
7. | Here the "ae" string prefix is more than company pride expressed by ACME Enterprises, it serves to prevent namespace collisions for fields that tend to have similar naming conventions. It is advisable to both use a similar prefix for your application as well as query the ODM for the proposed identifier. Using an alternative copy of the ODM and developing on test systems will reduce the odds of introducing the ill effects of namespace collisions in a production environment. |
8. | The names have been changed to protect those innocent of my hackery. |
Author: William Favorite <wfavorite@tablespace.net>
Version: 0.9.0
Date: 12/30/8