Using SYSLEVEL Files in Your Applications

Written by Martin Lafaix

What's that?
Determining a computer software configuration has always been a tricky process. It would be great to have a standard way for registering applications, so that we could easily query the system configuration. But wait a minute! OS/2 provides the SYSLEVEL command, which does just that (it even tells us the various component versions)! If only we could use it for our own purposes...

Why?
This registering ability proves its usefulness when writing (relatively) large projects or components. For example, if you write a public toolkit or tools other programs may use, it would be a good idea to register them. (OS/2 already uses it this way: on my home system, the syslevel command tells me that MMPM/2 and the Developer's toolkit are present - the same thing occurs if you have Communication Manager, etc.) When distributing such a tool, tell your users your component name and ID; it would then be easy for them to write an installation procedure which checks your component presence.

Another interest is that it helps component writers in creating CSD or component updates. As each SYSLEVEL file maintains a current CSD level as well as a previous CSD level field, it's easy to check what to update, and what to leave alone.

It's also useful while maintaining a computer network. It would provide the maintainer an up-to-date information database.

Contents
This article contains two parts. The first one describes the SYSLEVEL file format. The second part describes the two proposed APIs (one for C, and one for Rexx).

A Voyage to Syslevel
When you start SYSLEVEL.EXE, it scans all your drives for SYSLEVEL files. These files are named SYSLEVEL.xxx (where xxx is any three-letters extension), and have a specific header. When one such file is found, it is displayed, as shown in figure 1. So, to be able to use SYSLEVEL for our own purpose, we'll have to decrypt SYSLEVEL's file headers, and find out how (and where) to store our own data. C:\MMOS2\INSTALL\SYSLEVEL.MPM Multimedia Presentation Manager 2 Extensions systŠme 1.10    ID de composant 562137400 Type MMPM\2 Niveau de modifications en cours : UN00000 Niveau de modifications ant‚rieur : UN00000 ''Figure 1. Syslevel output sample (French version, sorry :-) )''

On the File Header
A SYSLEVEL file header looks like the following:

typedef struct _SYSLEVELHEADER { unsigned char h_magic[2]; unsigned char h_name[9]; unsigned char h_reserved1[4]; unsigned char h_updated; unsigned char h_reserved2[17]; ULONG        h_data; } SYSLEVELHEADER; ''Figure 2. The SYSLEVELHEADER structure.''

While I realize there's still many holes in there, it appears to fulfil our needs; namely, we are now able to create files recognized by SYSLEVEL.EXE. Isn't that great?
 * h_magic:is the magic cookie. Its value is 0xFFFF.
 * h_name:is the string "SYSLEVEL", in uppercase and null-terminated.
 * h_reserved1:meaning unknown. Set to zero.
 * h_updated:was 1 for OS2, GRE and MPM, and 0 for TLK. My guess: set it to zero.
 * h_reserved2:meaning unknown. Set to zero.
 * h_data:points to the beginning of the file's data structure (offset from the beginning of the file), which is described below.

But it's time to learn more...

The SYSLEVEL Data Structure
In the previous section, we have seen that the file's data structure starts at offset h_data. This structure (aka. the SYSLEVEL data structure) looks like the following: ''Figure 3. The SYSLEVELDATA structure.''


 * d_reserved1:set to zero...
 * d_kind:specifies the component "kind". It can be any of the following:
 * SLK_BASE (0)  "Base version"
 * SLK_EXTENSION (2)  "System extension"
 * SLK_STANDARD (15)  Neither "Base version" nor "System extension"... :-)
 * Any other value is equivalent to SLK_STANDARD (except 1, which is equivalent to SLK_EXTENSION).


 * d_version:specifies the component version. Its format is a bit strange:
 * the four high-order bits of d_version[0] contains the major version number, the four low-order bits contains the first minor version digit, and d_version[1] contains the second minor version digit.

For example, if d_version[0] is 0x21 and d_version[1] is 0x02, you will get "2.12".

[You can have a revision level, as in "2.12.a". See d_revision below.]


 * d_reserved2:oh no, not again. Set to zero.
 * d_clevel:specifies the current CSD level. It's a seven-byte string, usually in the form "AAC####", where AA is a two-byte string, C a country-dependent code, and #### a number. For example, on my home computer, I get "XRF2010" for the base operating system (It's the French version), and "XR02100" for the developer's toolkit (US version, this time).
 * It's just a suggested usage and is not enforced in any way. As long as you use seven printable characters, it should work.

Well, enough said on this exploration. It's time to go back to much safer places...
 * d_reserved3:guess what? You lose! Set to 0x5F this time.
 * d_plevel:specifies the previous CSD level. If there was no previous level; set it equal to the current level, d_clevel.
 * d_reserved4:is just like d_reserved3; set to 0x5F
 * d_title:specifies the component name. It's a null-terminated string.
 * d_cid:specifies the component ID. It's not null-terminated.
 * d_revision:specifies the component revision. It does not appear in the version field if sets to zero. If it's not null, it will be concatenated with major and minor version number, as in "1.2.3" (where 1 is the major version, 2 the minor and 3 the revision). If you prefer a letter in place of a digit, just increment the revision number by 0x10 for an upper-case letter, or by 0x30 for a lower-case. That is, if d_revision is 0x11, you will get an "A", and so on.
 * d_type:specifies the component type. It's an optional, null-terminated string. This field allows you to store additional informations for your component. For example, this field is used to differentiate the blue and salmon distribution of OS/2 (a type of '0-2' indicates the 'salmon' one).

Home, Sweet Home
This walk was fun, but all good things must come to an end. So we are back. And it's time to write some functions to comfort our precious new knowledge, isn't it?

Two APIs are available, a C one and a REXX one.

The SYSLEVEL C API
This section describes the API which provides access to the syslevel files. The corresponding code is in CLEVEL.ZIP. It's just an include file, level.h and a small C file, level.c which can be included, say, in your favourite library. It has been tested with GCC/2, but it should work with any other C compiler...

APIRET LvlOpenLevelFile(PSZ pszName, PHFILE phFile, ULONG ulOpenMode, PSZ pszCID) APIRET LvlCloseLevelFile(HFILE hFile) APIRET LvlQueryLevelFile (PSZ pszName, PSZ pszCID, PVOID pBuffer, ULONG ulBufSize) APIRET LvlQueryLevelData(HFILE hFile, ULONG ulWhat, PVOID pBuffer, ULONG ulBufSize, PULONG pulSize) APIRET LvlWriteLevelData(HFILE hFile, ULONG ulWhat, PVOID pBuffer, ULONG ulBufSize, PULONG pulSize)

Design considerations
This API has been modelled upon the profile (Prf) functions.

Examples
The following two figures show how to check for the existence of a component, and if a component needs updating, respectively. Figure 4. Syslevel API usage sample - Checking for the existence of a component Figure 5. Syslevel API usage sample - Shall we update component?

The SYSLEVEL REXX API
Well, it's not really an API, it's in fact a single function. The corresponding code is in RLEVEL.ZIP. It's just a REXX file, named REXXLVL.CMD. Put it somewhere along your PATH. The zip file also contains MAKESYSL.CMD, which can be used to create SYSLEVEL files.

This function has been modeled upon the STREAM function. RexxLvl

Example
The following example is a REXX script. It allows you to easily create syslevel files. Figure 6. Creating a syslevel file in REXX.

Summary
Now you have enough information to use the SYSLEVEL files well. However, the advertised advantages would become a reality only if we use SYSLEVEL files extensively. So, if you like these perspectives, use them, ask them, require them, again and again, until you get satisfaction. :-)

As a sidenote, the information exposed in the first part of this article is the result of my own exploration. If you find any inaccuracy or if you have more information, please, let me know.

kind

 * SLK_BASE:denotes a "base" component.
 * SLK_EXTENSION:denotes a system extension.
 * SLK_STANDARD:denotes a "standard" component. It's the suggested component kind value.

ulOpenMode - input

 * ULONG:field that describes the mode of the open function.
 * OLF_OPEN:open the specified file. The default behavior is to scan the current disk, starting from the current directory. It returns the first SYSLEVEL file whose extension matches pszName You can override this by combining OLF_OPEN with OLF_SCANDISK or OLF_CHECKID.
 * OLF_SCANDISKS:scans all disks (starting from C:). Use this flag to modify the default OLF_OPEN behavior.
 * OLF_CHECKID:finds file(s) whose ID matches the specified one. Use this flag to override the default OLF_OPEN behavior.
 * OLF_CREATE:creates the specified file in the current directory. A valid pszName and ID should be provided.

ulWhat - input
The following flags can be used when querying (or writing) a SYSLEVEL file.


 * QLD_MAJORVERSION:Query (or update) the major version field. It's a one-character field. It should be in the range '0'-'9'. The value is placed in (or taken from) the first character of pBuffer. (Buffer size should be at least 1.)
 * QLD_MINORVERSION:Query (or update) the minor version field. It's a two-character field. It should be in range '00'-'99'. The value is placed in (or taken from) the first two chars of pBuffer. (Buffer size should be at least 2.)
 * QLD_REVISION:Query (or update) the revision field. It's should fit in a character. If it's '0', there's no revision available. It can be a letter as well as a digit. The value is placed in (or taken from) the first character of pBuffer. (Buffer size should be at least 1.)
 * QLD_KIND:Query (or update) the kind field. The value is placed in (or taken from) the first character of *pBuffer. (Buffer size should be at least 1.)
 * QLD_CURRENTCSD:Query (or update) the current CSD level (when you update this field, its old value is copied to the old CSD level field). It's a seven-character field, and it does not have to be full-terminated. The value is placed in (or taken from) the first seven characters of pBuffer. (Buffer size should be at least 7.)
 * QLD_PREVIOUSCSD:Query the previous CSD level. You can't update this field. The value is placed in the first seven chars of pBuffer. (Buffer size should be at least 7.)
 * Note: CSD levels are not null-terminated. Be careful when using such a returned value.


 * QLD_TITLE:Query (or update) the component title field. It's an eighty-character string (required ending null included). The value is placed in (or taken from) the first eighty characters of pBuffer. On input, the buffer size should be at least 80. On output, the buffer size can exceed 80, but the written string is truncated (and null-terminated) to eighty characters.
 * QLD_ID:Query (or update) the component ID field. It's a nine-character field. It does not have to be null-terminated. The value is placed in (or taken from) the first nine characters of pBuffer. (Buffer size should be at least 9.)
 * Note: IDs are not null-terminated. Be careful when using such a returned value.


 * QLD_TYPE:Query (or update) the component type field.

LvlOpenLevelFile
APIRET LvlOpenLevelFile(PSZ pszName, PHFILE phFile, ULONG ulOpenMode, PSZ pszCID) Use this to find, open or create a SYSLEVEL file.

It returns the following values: 0  NO_ERROR 2  ERROR_FILE_NOT_FOUND 3  ERROR_PATH_NOT_FOUND 4  ERROR_TOO_MANY_OPEN_FILES 5  ERROR_ACCESS_DENIED 12 ERROR_INVALID_ACCESS 26 ERROR_NOT_DOS_DISK 32 ERROR_SHARING_VIOLATION 36 ERROR_SHARING_BUFFER_EXCEEDED 82 ERROR_CANNOT_MAKE 87 ERROR_INVALID_PARAMETER 99 ERROR_DEVICE_IN_USE 108 ERROR_DRIVE_LOCKED 110 ERROR_OPEN_FAILED 112 ERROR_DISK_FULL 206 ERROR_FILENAME_EXCEED_RANGE 231 ERROR_PIPE_BUSY

pszName - input
The syslevel file extension. It's a three-letter, null-terminated string. That is, LvlOpenLevelFile deals with syslevel files named syslevel.xxx, where xxx is pszName's value.

LvlCloseLevelFile
APIRET LvlCloseLevelFile(HFILE hFile)

Use this to close a SYSLEVEL file. It's just a DosClose alias, for API consistence.

It returns the following values: 0 NO_ERROR 2 ERROR_FILE_NOT_FOUND 5 ERROR_ACCESS_DENIED 6 ERROR_INVALID_HANDLE

LvlQueryLevelData
APIRET LvlQueryLevelData(HFILE hFile, ULONG ulWhat, PVOID pBuffer, ULONG ulBufSize, PULONG pulSize)

Use this to query an already opened SYSLEVEL file.

It returns the following values: 0 NO_ERROR 6 ERROR_INVALID_HANDLE 87 ERROR_INVALID_PARAMETER 122 ERROR_INSUFFICIENT_BUFFER When it returns ERROR_INSUFFICIENT_BUFFER, *pulSize contains the minimum requested size.

pBuffer - input/output
Address of the buffer that contains the data to write/read.

pulSize - output
Address of the variable to receive the number of bytes actually read or written.

LvlWriteLevelData
APIRET LvlWriteLevelData(HFILE hFile, ULONG ulWhat, PVOID pBuffer, ULONG ulBufSize, PULONG pulSize)

Use this to update an already opened SYSLEVEL file.

It returns the following values: 0 NO_ERROR 6 ERROR_INVALID_HANDLE 19 ERROR_WRITE_PROTECT 29 ERROR_WRITE_FAULT 87 ERROR_INVALID_PARAMETER 122 ERROR_INSUFFICIENT_BUFFER When it returns ERROR_INSUFFICIENT_BUFFER, *pulSize contains the minimum requested size.

ulBufSize - input
The length, in bytes, of pBuffer. This is the number of bytes to be read or write.

phFile - output
Address of the handle for the file.

pszCID - input
It's the component ID string. It's required when you specify the OLF_CHECKID flag when opening a syslevel flag. It's a nine-digit string, not null-terminated, which uniquely identifies the component in conjunction with pszName. That is, you can have multiple SYSLEVEL files with the same name, but you can't have multiple syslevel files with the same name and ID.

If you are not using the OLF_CHECKID flag, set pszCID to NULL.

RexxLvl
[Images:rexx-lvl.gif]

RexxLvl returns a string describing the state of, or the result of an operation upon, a SYSLEVEL file. This function is used to request information on the state of a SYSLEVEL file, or to carry out some specific operation on the SYSLEVEL file.

The first argument can be one of the following strings (of which only the first letter is needed) which describes the action to be carried out:


 * "c[lose]"::closes the specified syslevel file. File is a filename returned by a previous RexxLvl OPEN or NEW call.
 * "e[num]":enumerates the matching SYSLEVEL files. Extension is (an optional) SYSLEVEL file extension. Id is (an optional) component ID. It returns the number of matching files, or 'ERROR'. The corresponding filenames are pushed on the current queue. Use PULL or PARSE PULL to get them back.
 * "n[ew]":creates a new SYSLEVEL file, in the current directory. Extension is the SYSLEVEL file extension, and id is the component ID. It returns a file name to be used by subsequent RexxLvl calls.
 * "o[pen]":opens the specified SYSLEVEL file. Extension is the SYSLEVEL file extension, and id is the component ID. It returns a file name to be used by subsequent RexxLvl calls, 'NOTFOUND' if the required file was not found or 'ERROR' if an error occurred while searching.
 * Note: It returns the first matching SYSLEVEL file.

value - it is automatically sets when you update the c[csd] field). Value is the field's new value. It returns an empty string, or 'ERROR'.
 * "q[uery]":queries a SYSLEVEL file field. File is a filename returned by a previous RexxLvl OPEN or NEW call. Field is the field to be queried. It can be any of the following (of which only the first letter is needed):
 * "n[ame]":component name. Its length cannot exceed 79 characters.
 * "i[d]":component ID. It's a nine-digit string.
 * "k[ind]":component kind. It can be 0 (if the component is a base one), 1 or 2 (if the component is a system extension) or 15 otherwise.
 * "v[ersion]":component version. It should follows the 'x.yy[.z]' format.
 * "t[ype]":component type. It's a string.
 * "c[csd]":current CSD level. It's a seven-byte string.
 * "p[csd]":previous CSD level. It's a seven-byte string. It returns the field value or 'ERROR'.
 * "s[et]":sets the specified SYSLEVEL file field. File is a filename returned by a previous RexxLvl OPEN or NEW call. Field is the field to be set. It can be any of the values taken by the field parameter of the RexxLvl "query" call, except "p[csd]" (you can't set the "p[csd]" field's

LvlQueryLevelFile
APIRET LvlQueryLevelFile(PSZ pszName, PSZ pszCID, PVOID pBuffer, ULONG ulBufSize) Use this to query/find existing SYSLEVEL files. This function enumerates all the SYSLEVEL files present in the system and returns the names as a list in the pBuffer parameter. Each SYSLEVEL file name is terminated with a NULL character and the last name is terminated with two successive NULL characters. Specifying pszName or pszCID (or both) restricts the enumeration to the corresponding SYSLEVEL files.

It returns the following values: 0 NO_ERROR 87 ERROR_INVALID_PARAMETER 122 ERROR_INSUFFICIENT_BUFFER When it returns ERROR_INSUFFICIENT_BUFFER, the first ulBufSize bytes of pBuffer are correctly filled.