Jump to content

Using SYSLEVEL Files in Your Applications: Difference between revisions

From EDM2
No edit summary
Ak120 (talk | contribs)
No edit summary
Line 2: Line 2:


== Introduction ==
== Introduction ==
=== What's that? ===
=== 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...
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? ===
=== 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.


This registering ability proves its usefulness when writing (relatively)
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.
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
It's also useful while maintaining a computer network. It would provide the maintainer an up-to-date information database.
the maintainer an up-to-date information database.


=== Contents ===
=== 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).
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 ==
== 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
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 SYSLEVELs' file headers, and find out how
(and where) to store our own data.
(and where) to store our own data.
  C:\MMOS2\INSTALL\SYSLEVEL.MPM
  C:\MMOS2\INSTALL\SYSLEVEL.MPM
Line 52: Line 25:
  Niveau de modifications en cours : UN00000
  Niveau de modifications en cours : UN00000
  Niveau de modifications ant‚rieur : UN00000
  Niveau de modifications ant‚rieur : UN00000
 
''Figure 1.  Syslevel output sample (French version, sorry :-) )''
Figure 1.  Syslevel output sample (French version, sorry :-) )


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


Line 67: Line 38:
     ULONG        h_data;
     ULONG        h_data;
  } SYSLEVELHEADER;
  } SYSLEVELHEADER;
''Figure 2.  The SYSLEVELHEADER structure.''


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.


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


=== The SYSLEVEL Data Structure ===
=== 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:
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 {
  typedef struct _SYSLEVELDATA {
Line 108: Line 74:
     unsigned char d_type[1];
     unsigned char d_type[1];
  } SYSLEVELDATA;
  } SYSLEVELDATA;
''Figure 3.  The SYSLEVELDATA structure.''


Figure 3.  The SYSLEVELDATA structure.
;d_reserved1
 
:set to zero...
<dl>
;d_kind
<dt><b>d_reserved1</b><dd>set to zero...
:specifies the component "kind". It can be any of the following:
<dt><b>d_kind</b><dd>  specifies the component "kind". It can be any of the following:
* SLK_BASE (0)  "Base version"
* SLK_BASE (0)  "Base version"
* SLK_EXTENSION (2)  "System extension"
* SLK_EXTENSION (2)  "System extension"
* SLK_STANDARD (15)  Neither "Base version" nor "System extension"... :-)
* SLK_STANDARD (15)  Neither "Base version" nor "System extension"... :-)


Any other value is equivalent to SLK_STANDARD (except 1, which is
Any other value is equivalent to SLK_STANDARD (except 1, which is equivalent to SLK_EXTENSION).
equivalent to SLK_EXTENSION).


<dt><b>d_version</b><dd>  specifies the component version. Its format is a bit strange:
;d_version
the four high-order bits of <b>d_version[0]</b> contains the major version number,
:specifies the component version. Its format is a bit strange:
the four low-order bits contains the first minor version digit, and
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 <b>d_version[1]</b> contains the second minor version digit.
<b>d_version[1]</b> contains the second minor version digit.


For example, if <b>d_version[0]</b> is 0x21 and d_version[1] is 0x02, you will get
For example, if '''d_version[0]''' is 0x21 and d_version[1] is 0x02, you will get "2.12".
"2.12".


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


<dt><b>d_reserved2</b><dd>  oh no, not again.  Set to zero.
;d_reserved2
 
:oh no, not again.  Set to zero.
<dt><b>d_clevel</b><dd>  specifies the current CSD level. It's a seven-byte string,
;d_clevel
usually in the form "AAC####", where AA is a two-byte string, C a
: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).
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.
 
<dt><b>d_reserved3</b><dd>  guess what?  You lose!  Set to 0x5F this time.
 
<dt><b>d_plevel</b><dd>  specifies the previous CSD level.  If there was no previous
level; set it equal to the current level, d_clevel.


<dt><b>d_reserved4</b><dd>  is just like d_reserved3; set to 0x5F
It's just a suggested usage and is not enforced in any way. As long as you use seven printable characters, it should work.


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


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


== Home, Sweet Home ==
== 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?
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.
Two APIs are available, a C one and a REXX one.


=== The SYSLEVEL C API ===
=== 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 favorite library. It has been tested with GCC/2, but it should work with any other C compiler...
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
favorite 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 LvlOpenLevelFile(PSZ pszName, PHFILE phFile, ULONG ulOpenMode, PSZ pszCID)
Line 198: Line 138:


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


==== Examples ====
==== Examples ====
 
The following two figures show how to check for the existence of a component, and if a component needs updating, respectively.
The following two figures show how to check for the existence of a
component, and if a component needs updating, respectively.


  :
  :
Line 249: Line 186:


=== The SYSLEVEL REXX API ===
=== 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.
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.
This function has been modeled upon the STREAM function.
Line 260: Line 193:


==== Example ====
==== Example ====
 
The following example is a REXX script. It allows you to easily create syslevel files.
The following example is a REXX script. It allows you to easily create
syslevel files.


  /* makesysl.cmd                                          940327 */
  /* makesysl.cmd                                          940327 */
Line 285: Line 216:


== Summary ==
== 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.  :-)


Now you have enough information to use the SYSLEVEL files well.  However,
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.
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 ===
=== kind ===
<dl>
;SLK_BASE
<dt><b>SLK_BASE</b><dd>denotes a "base" component.
:denotes a "base" component.
 
;SLK_EXTENSION
<dt><b>SLK_EXTENSION</b><dd>denotes a system extension.
:denotes a system extension.
 
;SLK_STANDARD
<dt><b>SLK_STANDARD</b><dd>denotes a "standard" component. It's the suggested
:denotes a "standard" component. It's the suggested component kind value.
component kind value.
</dl>


=== ulOpenMode - input ===
=== ulOpenMode - input ===
<dl>
;ULONG
<dt><b>ULONG</b><dd>field that describes the mode of the open function.
:field that describes the mode of the open function.
 
;OLF_OPEN
<dt><b>OLF_OPEN</b><dd>open the specified file. The default behavior is to scan the
: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.
current disk, starting from the current directory. It returns the first
;OLF_SCANDISKS
SYSLEVEL file whose extension matches pszName  You can override this by
:scans all disks (starting from C:). Use this flag to modify the default OLF_OPEN behavior.
combining OLF_OPEN with OLF_SCANDISK or OLF_CHECKID.
;OLF_CHECKID
 
:finds file(s) whose ID matches the specified one. Use this flag to override the default OLF_OPEN behavior.
<dt><b>OLF_SCANDISKS</b><dd>scans all disks (starting from C:). Use this flag to
;OLF_CREATE
modify the default OLF_OPEN behavior.
:creates the specified file in the current directory. A valid pszName and ID should be provided.
 
<dt><b>OLF_CHECKID</b><dd>finds file(s) whose ID matches the specified one. Use this
flag to override the default OLF_OPEN behavior.
 
<dt><b>OLF_CREATE</b><dd>creates the specified file in the current directory. A valid
pszName and ID should be provided.
</dl>


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


<dl>
;QLD_MAJORVERSION
<dt><b>QLD_MAJORVERSION</b><dd>Query (or update) the major version field. It's a
: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.)
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.)


<dt><b>QLD_MINORVERSION</b><dd>Query (or update) the minor version field. It's a
;QLD_MINORVERSION
two-character field. It should be in range '00'-'99'. The value is placed
: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.)
in (or taken from) the first two chars of pBuffer. (Buffer size should be
at least 2.)


<dt><b>QLD_REVISION</b><dd>Query (or update) the revision field. It's should fit in a
;QLD_REVISION
character. If it's '0', there's no revision available. It can be a letter
: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.)
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.)


<dt><b>QLD_KIND</b><dd>Query (or update) the kind field. The value is placed in (or
;QLD_KIND
taken from) the first character of *pBuffer. (Buffer size should be at
: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.)
least 1.)


<dt><b>QLD_CURRENTCSD</b><dd>Query (or update) the current CSD level (when you update
;QLD_CURRENTCSD
this field, its old value is copied to the old CSD level field). It's a
: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.
seven-character field, and it does not have to be null-terminated. The
value is placed in (or taken from) the first seven characters of pBuffer.
(Buffer size should be at least 7.)
(Buffer size should be at least 7.)


<dt><b>QLD_PREVIOUSCSD</b><dd>Query the previous CSD level. You can't update this
;QLD_PREVIOUSCSD
field. The value is placed in the first seven chars of pBuffer. (Buffer
: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.)
size should be at least 7.)
 
<br>
Note: CSD levels are not null-terminated. Be careful when using such a returned value.
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.


<dt><b>QLD_TITLE</b><dd>Query (or update) the component title field. It's an
;QLD_ID
eighty-character string (required ending null included). The value is
: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.)
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.


<dt><b>QLD_ID</b><dd>Query (or update) the component ID field.  It's a nine-character
Note: IDs are not null-terminated. Be careful when using such a returned value.
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.)
<br>
Note: IDs are not null-terminated. Be careful when using such a returned
value.


<dt><b>QLD_TYPE</b><dd>Query (or update) the component type field.
;QLD_TYPE
</dl>
:Query (or update) the component type field.


== LvlOpenLevelFile ==
== LvlOpenLevelFile ==
Line 406: Line 302:


=== pszName - input ===
=== 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.
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 ==
== LvlCloseLevelFile ==
Line 415: Line 308:
  APIRET LvlCloseLevelFile(HFILE hFile)
  APIRET LvlCloseLevelFile(HFILE hFile)


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


It returns the following values:
It returns the following values:
Line 438: Line 330:
  122  ERROR_INSUFFICIENT_BUFFER
  122  ERROR_INSUFFICIENT_BUFFER
   
   
When it returns ERROR_INSUFFICIENT_BUFFER, *pulSize contains the minimum
When it returns ERROR_INSUFFICIENT_BUFFER, *pulSize contains the minimum requested size.
requested size.


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


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


== LvlWriteLevelData ==
== LvlWriteLevelData ==
Line 465: Line 353:
  122  ERROR_INSUFFICIENT_BUFFER
  122  ERROR_INSUFFICIENT_BUFFER


When it returns ERROR_INSUFFICIENT_BUFFER, *pulSize contains the minimum
When it returns ERROR_INSUFFICIENT_BUFFER, *pulSize contains the minimum requested size.
requested size.


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


=== phFile - output ===
=== phFile - output ===
Address of the handle for the file.
Address of the handle for the file.


=== pszCID - input ===
=== 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.
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.
If you are not using the OLF_CHECKID flag, set pszCID to NULL.


== RexxLvl() ==
== RexxLvl() ==
[Images:rexx-lvl.gif]


[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:


RexxLvl returns a string describing the state of, or the result of an
;"c[lose]"
operation upon, a SYSLEVEL file. This function is used to request
:closes the specified syslevel file. File is a filename returned by a previous RexxLvl OPEN or NEW call.
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
;"e[num]"
first letter is needed) which describes the action to be carried out:
: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.


<dl>
;"n[ew]"
<dt><b>"c[lose]"</b><dd>closes the specified syslevel file. File is a filename
: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.
returned by a previous RexxLvl OPEN or NEW call.


<dt><b>"e[num]"</b><dd>enumerates the matching SYSLEVEL files. Extension is (an
;"o[pen]"
optional) SYSLEVEL file extension.  Id is (an optional) component ID. It
: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.
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.


<dt><b>"n[ew]"</b><dd>creates a new SYSLEVEL file, in the current directory. Extension
Note: It returns the first matching SYSLEVEL file.
is the SYSLEVEL file extension, and id is the component ID.  It returns a
file name to be used by subsequent RexxLvl calls.


<dt><b>"o[pen]"</b><dd>opens the specified SYSLEVEL file. Extension is the SYSLEVEL
;"q[uery]"
file extension, and id is the component ID. It returns a file name to be
: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):
used by subsequent RexxLvl calls, 'NOTFOUND' if the required file was not
found or 'ERROR' if an error occurred while searching.
<br>
Note:  It returns the first matching SYSLEVEL file.


<dt><b>"q[uery]"</b><dd>queries a SYSLEVEL file field.  File is a filename returned by
;"n[ame]"
a previous RexxLvl OPEN or NEW call. Field is the field to be queried. It
:component name. Its length cannot exceed 79 characters.
can be any of the following (of which only the first letter is needed):


<dt><b>"n[ame]"</b><dd>component name. Its length cannot exceed 79 characters.
;"i[d]"
:component ID. It's a nine-digit string.


<dt><b>"i[d]"</b><dd>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.


<dt><b>"k[ind]"</b><dd>component kind. It can be 0 (if the component is a base one), 1
;"v[ersion]"
or 2 (if the component is a system extension) or 15 otherwise.
:component version. It should follows the 'x.yy[.z]' format.


<dt><b>"v[ersion]"</b><dd>component version. It should follows the 'x.yy[.z]' format.
;"t[ype]"
:component type. It's a string.


<dt><b>"t[ype]"</b><dd>component type. It's a string.
;"c[csd]"
:current CSD level. It's a seven-byte string.


<dt><b>"c[csd]"</b><dd>current CSD level. It's a seven-byte string.
;"p[csd]"
:previous CSD level. It's a seven-byte string.


<dt><b>"p[csd]"</b><dd>previous CSD level.  It's a seven-byte string.
<br>
It returns the field value or 'ERROR'.
It returns the field value or 'ERROR'.


<dt><b>"s[et]"</b><dd>sets the specified SYSLEVEL file field. File is a filename
;"s[et]"
returned by a previous RexxLvl OPEN or NEW call. Field is the field to be
: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
set. It can be any of the values taken by the field parameter of the
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'.
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'.
</dl>


== LvlQueryLevelFile ==
== LvlQueryLevelFile ==
Line 553: Line 421:
  APIRET LvlQueryLevelFile(PSZ pszName, PSZ pszCID, PVOID pBuffer, ULONG ulBufSize)
  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
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.
enumeration to the corresponding SYSLEVEL files.


Line 567: Line 430:
  122  ERROR_INSUFFICIENT_BUFFER
  122  ERROR_INSUFFICIENT_BUFFER


When it returns ERROR_INSUFFICIENT_BUFFER, the first ulBufSize bytes of
When it returns ERROR_INSUFFICIENT_BUFFER, the first ulBufSize bytes of pBuffer are correctly filled.
pBuffer are correctly filled.


[[Category:Languages Articles]]
[[Category:Languages Articles]]

Revision as of 23:29, 4 April 2016

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

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 favorite 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 modeled 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.