Using SYSLEVEL Files in Your Applications

From EDM2
Jump to: navigation, search

Written by Martin Lafaix

Introduction

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.

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.

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?

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:

typedef struct _SYSLEVELDATA {
   unsigned char d_reserved1[2];
   unsigned char d_kind;
   unsigned char d_version[2];
   unsigned char d_reserved2[2];
   unsigned char d_clevel[7];
   unsigned char d_reserved3;
   unsigned char d_plevel[7];
   unsigned char d_reserved4;
   unsigned char d_title[80];
   unsigned char d_cid[9];
   unsigned char d_revision;
   unsigned char d_type[1];
} SYSLEVELDATA;

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.
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 information 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).

Well, enough said on this exploration. It's time to go back to much safer places...

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.

:
#include <level.h>
:
int main()
{
  HFILE hFile;
  :
  if(hFile = LvlOpenLevelFile("DBM",
                              OLF_SCANDISKS | OLF_CHECKID,
                              "123456789"))
    /* present */
    :
  else
    /* not present */
    :
  :
  LvlCloseLevelFile(hFile);
  :
}

Figure 4. Syslevel API usage sample - Checking for the existence of a component

:
#include <level.h>

:
BOOL _needUpdate(HFILE hFile)
{
  CHAR achCSD[7];
  ULONG ulCount;

  if(LvlQueryLevelData(hFile, QLD_CURRENTCSD, achCSD, 7, &ulCount))
    /* shall we update it? */
    return strncmp(achCSD, "DBF0099",7) <= 0; /* for example */
  else
    /* an error occurred */
    :
}
:

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.

/* makesysl.cmd                                          940327 */

say 'Component name:';                  parse pull name
say 'Component ID:';                    parse pull ID
say 'Component type (optional):';       parse pull type
say 'Component version (as x.y[.z]):';  parse pull version
say 'Current CSD Level:';               parse pull CSD
say 'Component extension:';             parse pull ext

file = RexxLvl("n", ext, ID)
call RexxLvl "s", file, "n", name
call RexxLvl "s", file, "c", csd
call RexxLvl "s", file, "v", version
call RexxLvl "s", file, "t", type
rc = RexxLvl("c", file)

if rc = 0 then say 'SYSLEVEL.'ext' created.'

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.
"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

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'.

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.