asprintf for OpenVMS

Wednesday, 21 December 2011 11:45

For a long time the C Run-Time Library that ships with OpenVMS and the HP C compiler has been behind the curve.  It usually takes a number of years to catch up with POSIX functionality, and in many cases it never catches up with GNU or *BSD extensions to the library.  It took years to get an snprintf routine in the library, and then it was never backported to OpenVMS VAX or earlier versions of OpenVMS Alpha.  Unfortunately [v]asprintf is no exception to this rule.

The short version of this story is that I was poking around in some OpenBSD code and came across asprintf.  Curious as to what this *printf variant did I checked out the man page and was pleasantly surprised to find it was a version of sprintf that dynamically allocated its storage.  So, after some looking around I discovered that it was not available on OpenVMS (not that I was particularly shocked) and decided to implement my own.

Of course, there are implementations around the 'net that I could probably hook up to sprintf, etc.  However, there were a couple concerns that forced me to look elsewhere to solve this problem.  The first was perfomance, and the second was that there may be issues with floating point representations and other output formats.  It was important to me that this behaved just like the OpenVMS *printf routines.  So, I started looking in the source listings.

After some quick poking around in the [ACRTL] facility the module that held the guts of the *printf engine was discovered as [ACRTL]F_DOPRINT.C.  So, after reading through the listings (in [ACRTL.LIS]*_DOPRINT.*) I came up with the following (annotated) function definition:

/*
** DECC$$DOPRINT
**
**     This global routine compiled only for narrow printf.
**
** Input Parameters:
**     resbuf         - address of result buffer
**     fmtstr         - address of format string
**     arglist        - address of argument list of items to be converted
**     max_buflen     - maximum length of result buffer
**     fcb            - Strictly speaking this is an FCB/WINDOW address for
**                      for a file, think fprintf() and friends (0 for SPRINTF).
**                      However, none of the code inside decc$$doprint() touches
**                      it. It is simply a pointer that is passed to the
**                      partial_printf routine. It could actually be used to
**                      provide context to the partial_printf routine.
**     partial_printf - printf call back address. This routine is used to
**                      flush the buffer once it is filled. It returns 0
**                      for error and 1 for success.
**
** Output:
**     bytes_left     - pointer to memory holding the number of bytes in the
**                      last partial filled buffer.  For the VAX C RTL,
**                      this is actually returned in register R1.
**     convert/formatted text - in buffer
**     # of formatted bytes in left - bytes_left
**
** Returns:
**     # of bytes transmitted - on success
**                -1          - on failure
*/
   extern int decc$$doprint(char *resbuf, const char *fmtstr,
                          va_list arglist, int max_buflen, void *fcb,
                          int (*partial_printf)(void *fcb, char *buffer, int en),
                          ... /* int *bytes_left */);

Next I had a look through the various libraries in SYS$LIBRARY: and found that this function was in fact publically available.  So, I wrote my own implementation that supplied a partial_printf function that buffered the formatted output to a realloc'd buffer and returned it to the caller. The initial version of which only worked with G_Float format floating point numbers.  After the additions of some #ifdef's based on the contents of [ACRTL]F_DORPINT.SRC the code now compiles correctly when compiling for all different floating point combinations.  The code is shown below.

#ifdef VAX
# ifdef VAX11C
#  if CC$gfloat
#   define decc$$doprint c$$gdoprint
#  else
#   define decc$$doprint c$$ddoprint
#  endif
# else
#  if __G_FLOAT
#   define decc$$doprint decc$$gdoprint
#  else
#   define decc$$doprint decc$$ddoprint
#  endif
# endif
#else
# if __X_FLOAT
#  if __G_FLOAT
#   define decc$$doprint decc$$gxdoprint
#  elif __IEEE_FLOAT
#   define decc$$doprint decc$$txdoprint
#  else
#   define decc$$doprint decc$$dxdoprint
#  endif
# else
#  if __G_FLOAT
#   define decc$$doprint decc$$gdoprint
#  elif __IEEE_FLOAT
#   define decc$$doprint decc$$tdoprint
#  else
#   define decc$$doprint decc$$ddoprint
#  endif
# endif
#endif

Later on, after looking at some of the comments in the source listings it became clear that the *printf engine in use in the C Run-Time Library may have had its heritage in the the VAX C RTL.  I checked this out and found that the VAX C routine was actually implemented in MACRO-32.  However, the argument lists were almost the same, the only major difference being that the bytes_left parameter was actually returned in R1.  So, after a couple quick changes, the end result was that this code will also work with versions of the VAX C RTL released after June 1986.

The only thing that really surprises me about all this is that it took until February, 2003 to get [v]snprintf into the CRTL and that [v]asprintf was never implemented, when it is not really a very hard problem to solve.

Building

For each different combination of compilers and architectures, there is a different set of build instructions.  Although these appear in the source module, they are also available below.

When using the VAX C Run-Time Library and compiler, it is important to use the following compilation command as compiling without the /OPTIMIZE qualifier will cause the code to ACCVIO.

$ CC/VAXC/OPTIMIZE=NOINLINE ASPRINTF.C
$ LINK ...,ASPRINTF,SYS$LIBRARY:VAXCRTL/LIBRARY -
       /INCLUDE=(C$$DDOPRINT,C$$GDOPRINT)

There are no special qualifiers for the compilation under OpenVMS VAX, Alpha or I64 using the DEC/Compaq/HP C compiler.  However, there are special linking qualifiers.  For VAX it is necessary to use these qualifiers:

$ LINK ...,ASPRINTF,SYS$LIBRARY:DECC$CRTL/LIBRARY -
       /INCLUDE=(C$$DOPRINT,C$$GDOPRINT)

For Alpha it is necessary to use these qualifiers:

$ LINK ...,ASPRINTF,SYS$LIBRARY:DECC$CRTL/LIBRARY -
       /INCLUDE=(C$$DDOPRINT,C$$DXDOPRINT,C$$GDOPRINT, -
                 C$$GXDOPRINT,C$$TDOPRINT,C$$TXDOPRINT)

And finally, for I64 the following linker qualifiers are needed:

$ LINK ...,ASPRINTF,SYS$LIBRARY:SYS$LIBRARY:STARLET/LIBRARY -
       /INCLUDE=(C$$DDOPRINT,C$$DXDOPRINT,C$$GDOPRINT, -
                 C$$GXDOPRINT,C$$TDOPRINT,C$$TXDOPRINT)

Source

The source code for [v]asprintf for OpenVMS can be downloaded here.  If you are an OpenVMS engineer, feel free to take this code and add it to the C Run-Time Library.  See the licensing comments below, it is all yours!

Support

All the code mentioned in this blog post is released under an MIT-style license.  There is no actual support, but I am generally happy to look into any discovered bugs.  Beware, that given this code call into an undocumented and unsupported part of the CRTL, your particular issue may not be fixable.  Only small, backward compatible changes have been made over the years, so the likelyhood of something going wrong and not being fixable is pretty small.

References

The following resources were instrumental in the production of this blog post.

  • The OpenVMS VAX V7.2 and OpenVMS Alpha V7.3-2 source listings, in particular the following modules:
    • [ACRTL.LIS]*F_DOPRINT.LIS, F_DOPRINT.SRC
    • [CRTL.LIS]*F_DOPRINT.LIS, F_DOPRINT.SRC
    • [VAXCRTL.LIS]DOPRINT.LIS;
  • the Wikipedia article for 'The C Programming Language' cover image used for this article's artwork;
  • JF Mezei for the Vernon image also used in the same artwork; and
  • lastly, OpenBSD for sparking my interest in this function in the first place!

[PRINT]  [PRINT]