# # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved. # Mapfiles and versioning in illumos ================================== 1.0 Objective of this README This README describes the engineering practices of creating and updating visible library interfaces. It describes various kinds of actions that typically occur as libraries are evolved, and shows how interface specifications are affected or updated in accordance. It tells you what you must do as a shared library developer if you: 1. Make interface additions to an existing library - add a Public interface - add a Private interface 2. Update an interface in an existing library - remove an existing interface - promote a Private interface to Public - scope a Private interface to local - move an interface from one library to another - copy interfaces which are part of the standard to a new or existing library 3. Introduce a new library - source directory hierarchy - creation of the "mapfile-vers" file - Makefiles 4. Make an entire library obsolete before end-of-life - introduce SUNWobsolete to the "mapfile-vers" file ------------------------------------------------------------------------------- 2.0 What's a mapfile? Mapfiles are used to tell the link-editor ("ld") all sorts of things about how to generate an executable file or a shared object from a collection of relocatable objects, such as generated by a compiler. For all the gory details, see the Solaris Linker and Libraries Guide. There are two versions of the mapfile language accepted by the link-editor. Version 1 derives from AT&T System V Release 4 Unix. Version 2 is a newer syntax specific to Solaris and derivatives. All mapfiles in illumos are required to use version 2 syntax. Note that every mapfile using version 2 syntax must start with the line: $mapfile_version 2 Here, we are only concerned with specifying externally-visible interfaces for shared libraries (shared objects) and with specifying their versions for ABI (Application Binary Interface) purposes. For these purposes, we only need to deal with a subset of the mapfile language. There should be a "mapfile-vers" file associated with every shared library and it should reside in the common source directory for that library, most often in a "common" directory. This is the usual layout of a library's top-level directory (usr/src/lib/libwombat): Makefile amd64/ i386/ sparcv9/ Makefile.com common/ sparc/ The "common" directory contains the source files and other common files for the library: bat.c libwombat_impl.h mapfile-vers wom.c libwombat.h llib-lwombat util.c wombat.c The mapfile's name is, by convention, "mapfile-vers" because it is used for only two purposes: to specify externally-visible interface names while suppressing visibility of all other names, and to specify their respective unique version names. ------------------------------------------------------------------------------- 3.0 Contents of mapfile-vers The structure of mapfile-vers is best explained by an example (the license notification and copyright notice is omitted here for brevity): $mapfile_version 2 SYMBOL_VERSION ILLUMOS_0.2 { # Second interface change in illumos global: wb_notify; } ILLUMOS_0.1; SYMBOL_VERSION ILLUMOS_0.1 { # First interface change in illumos global: wb_poll; } SUNW_1.2; SYMBOL_VERSION SUNW_1.2 { # update to libwombat, Solaris 10 global: wb_readv; wb_stat; wb_writev; } SUNW_1.1; SYMBOL_VERSION SUNW_1.1 { # first release of libwombat, Solaris 9 global: wb_read; wb_write; }; SYMBOL_VERSION SUNWprivate { # private libwombat symbols global: wb_add; wb_delete; wb_search; local: *; }; Each of these sections is a version declaration describing an ABI version of the library containing the set of symbols exposed by the library to external callers. ABI versions must be constant, that is version ILLUMOS_0.2 in a given library must always describe the same interface such that applications may safely state their dependencies in terms of these versions and have a constant and predictable ABI be exposed. This in effect means that once a version is publicly visible, it may never be removed or have symbols added to or removed from it. ABI versions with the same major number should be upward compatible, that is ILLUMOS_0.3 of a given library must contain all the interfaces in ILLUMOS_0.2, and they must be compatible. The private version, however, is special, and describes any private yet exposed symbols within a library, and may change at any time (and without notice). It exists purely to allow certain symbols to be of global scope but not Public. Similarly, any symbols scoped local are private and may change safely, even if the local statement happens to be within a public version. Interface changes made in illumos should be done with ILLUMOS_0.* versions, introducing one version per interface change. In illumos, unlike Solaris, symbol versions are not release-specific because of the requirement that each be constant. No change should be made to add or remove symbols from any pre-existing Public version. The SUNW_*.* names were the Public version names of the library in Solaris. There should be at most one version name for each release of Solaris, with the minor number incremented by one over the previous version. No changes should ever be made to SUNW_1.* versions. So, for example, to add a new interface to libwombat in illumos one would add: SYMBOL_VERSION ILLUMOS_0.3 { # Third update to libwombat in illumos global: wb_lseek; } ILLUMOS_0.2; Each version must inherit all symbols from its preceding version, specified at the ending "}" for each version. The initial public version does not inherit any symbols. The private version named either "SUNWprivate" for libraries with private symbols pre-existing illumos, or "ILLUMOSprivate" otherwise stands alone, inheriting nothing and being inherited by nothing. The two lines in SUNWprivate: local: *; ensure that no symbols other than those listed in the mapfile are visible to clients of the library. If there is no private version, these two lines should appear in the first public version. For maintainability, the list of names in each version block should be sorted in dictionary order (sort -d). Please comply. The version 2 mapfile language supports a simple mechanism for conditional input, in which lines in the mapfile apply only to a specific platform or ELFCLASS (32/64-bit). This mechanism works very much like the #if/#endif feature of the C preprocessor. For instance, the following mapfile declares a version SUNW_1.1 that always exports a symbol foo, and also exports the symbol bar on 32-bit sparc platforms: $mapfile_version 2 SYMBOL_VERSION SUNW_1.1 { foo; $if _sparc && _ELF32 bar; $endif }; Conditional input can be used if there are ISA-specific library interfaces not common to all instances of the library. It is the preferred method for expressing platform specific items, as long as the differences are simple (which is almost always the case). For example, see libproc, or, if you are masochistic, libc or libnsl. In general, use of this feature should be minimal. In addition to conditional input, there is a second heavier weight mechanism for expressing ISA-specific differences. In addition to the common mapfile: common/mapfile-vers some libraries may have ISA-specific supplemental mapfiles, one in each of the ISA directories: amd64/mapfile-vers i386/mapfile-vers sparc/mapfile-vers sparcv9/mapfile-vers The ISA-specific mapfiles look like the common mapfile, except that only the ISA-specific names appear. The version names are the same as those in the common mapfile, but only non-empty version instances are present and no inheritance specification is present. The link-editor reads the information from the common and ISA-specific mapfiles and merges them in memory into a single description used to create the resulting object. ISA-specific mapfiles were used with the version 1 mapfile language, which lacked conditional input. Their use is rare now, as conditional input is generally preferred. However, it is important to use conditional input carefully, or the resulting mapfile can be extremly difficult to read. ------------------------------------------------------------------------------- 4.0 Making interface additions to an existing library 4.1 Adding a Public interface Public interfaces should be added to a new ILLUMOS_ symbol version, with the minor number incremented by one from the current highest version name. If this is the first Public interface in the shared object, a new ILLUMOS_0.1 version name must be introduced. The major revision number is incremented whenever an incompatible change is made to an interface. This could be the case if an API changes so dramatically as to invalidate dependencies. This should almost never occur in practice. It also requires changing the suffix of the shared object from, say, .so.1 to .so.2 and introducing code to continue to ship the .so.1 version of the library. The minor revision number is incremented whenever one or more new interfaces is added to a library. Once a version comes to exist in illumos, it is from that point onward considered to be immutable. 4.2 Adding a Private interface Private interfaces are the non-ABI interfaces of the library. Unlike introducing a Public interface, a new entry is simply added to the private version. No minor number increment is necessary. If this interface happens to be the first Private interface introduced into the library, the private version must be created (with no major.minor version numbers). It inherits nothing, nothing inherits from it and it should be named ILLUMOSprivate. If the library already has Private interfaces in a SUNWprivate version, you should use that. They may have numbered version names like SUNWprivate_m.n (due to errors of the past). If so, just use the highest numbered private version name to version the new interface. There is no need to introduce a new private version name. Be careful not to use a lower numbered private version name; doing so can cause runtime errors (as opposed to load time errors) when running an application with older versions of the library. There are also libraries in illumos that contain only private interfaces. In such libraries, the private versions maybe legitimately be versioned and they may be incremented to ensure that the programs that depend on them are built and delivered as a integrated unit. A notable example of this is libld.so (usr/src/cmd/sgs/libld), which contains the implementation of the link-editor, the public interface to which is provided by the ld command. When making a modification to the interface of such a library, you should follow the convention already in place. 4.3 Historical handling of Solaris update releases. To aid the understanding of our existing mapfiles, it is useful to note how interface versions were handled as they interacted with update releases of Solaris. Solaris update releases required careful coordination with the full release currently under development to keep symbol versions constant between releases. Multiple update releases were generally shipped during the development of the next full release of Solaris. It was impossible to know in advance the full set of new interfaces in the next full release until it was complete. Some, though not all, new interfaces were included in the intervening update releases between full releases. Consequently, the new version number for an update cannot be a minor increment, but must be a micro increment to ensure that was a distinct version between the two releases. For example, if Release N had version number SUNW_1.3 and Release N+1 had SUNW_1.4, then interfaces added to an update of Release N must have micro numbers such as SUNW_1.3.1, SUNW_1.3.2, etc. (note that the micro number is not directly tied to the update number: SUNW_1.3.1 may have appeared in Update 2). The micro versions form an inheritance chain that is inserted between two successive minor versions. For example, the mapfile-vers file for minor release "N+1" to reflect its inclusion of micro releases will look like the following: $mapfile_version 2 SYMBOL_VERSION SUNW_1.4 { # release N+1 global: ... } SUNW_1.3.2; SYMBOL_VERSION SUNW_1.3.2 { # micro release 2 (e.g., release NU3) global: ... } SUNW_1.3.1; SYMBOL_VERSION SUNW_1.3.1 { # micro release 1 (e.g., release NU2) global: ... } SUNW_1.3; SYMBOL_VERSION SUNW_1.3 { # release N global: ... } SUNW_1.2; SYMBOL_VERSION SUNW_1.2 { # release N-1 global: ... } SUNW_1.1; SYMBOL_VERSION SUNW_1.1 { # first release global: ... }; SYMBOL_VERSION SUNW_private { # same in all releases global: ... local: *; }; The corresponding update/patch mapfile-vers file will be identical except for the exclusion of SUNW_1.4. Those interfaces which are only present in Release N+1 are always put into the next minor version set, SUNW_1.4. Thus when adding a new Public interface to an update release, both the mapfiles of the update release and next full release should have been modified to be consistent. There have been several cases of accidental deviation from this scheme, and existing mapfiles sometimes reflect this unfortunate state of affairs. ------------------------------------------------------------------------------- 5.0 How to update an interface in an existing library 5.1 Removing an existing interface 5.1.1 Moving a Public interface No Public interfaces should ever be removed from any mapfile, as this will break all existing consumers of that interface. To move an interface from one library to (say) libc, the code has to be deleted from the library and added to libc, then the mapfile for the library has to have the interface's entry changed from: getfoobar; to: getfoobar { TYPE = FUNCTION; FILTER = libc.so.1 }; This is an exception to the immutability of public symbol versions. See, for example, libnsl's common/mapfile-vers file. Follow the rules for adding a new interface for the necessary changes to libc's mapfile to accommodate the moved interface, including creating a new version in libc for the symbol. When merging an entire library into libc, the mapfile is changed to specify the type of each public symbol similarly to the above: getfoobar; to: getfoobar { TYPE = FUNCTION }; But rather than specifying the library on which we filter for each symbol, the link-editor is invoked with '-F libc.so.1' to specify that our entire symbol table is a filter on libc. For examples, see libaio and librt. 5.1.2 Removing a Private interface Deletion of Private interfaces is allowed, but caution should be taken; it should first be established that the interface is not being used. To remove a Private interface, simply delete the corresponding entry for that symbol from the mapfile's private version section. Do not forget to delete these Public or Private interfaces from the library's header files as well as from the code that implements the interfaces. 5.2 Promoting a Private interface to Public This is similar to what's done when adding a Public interface. Promoting an existing Private interface to a Public one only requires a change to the existing interface definition. Private interfaces have the symbol version name "ILLUMOSprivate" or "SUNWprivate" associated with them. To make the interface a Public one, the interface must be added as if it were a new public symbol, following those same rules and removed from the private version. As an example, if we were modifying libwombat.so.1 and its existing latest version were ILLUMOS_0.3, any new ABI would be put into a version called ILLUMOS_0.4. Therefore, whether you wish to promote an existing Private interface to Public, or to introduce a new Public interface, this (next successive minor numbered version level) would be the version that it would be associated with. 5.3 Scoping a Private interface local Any interfaces not present in the mapfile-vers file will be scoped local due to the presence of the local: *; lines discussed earlier. This ensures that such interfaces will not be visible outside the library. To move an interface from Private to local scope, simply remove the Private interface from the mapfile-vers file and the header file to prevent it from being exported. This may require moving the Private interface into a library-private header file. Scope reduction of Public interfaces is forbidden. For the interface to be used in more than one file within the library, it should be in a header file that can be included by each file in the library that uses the interface. For example: #include "libprivate.h" 5.4 How to copy interfaces which are part of a standard to a new or existing library SYSVABI and SISCD are reserved version names for interfaces listed in the System V Interface Definition and the Sparc Compliance Definition. Avoid using these version names when copying the implementation of standard interfaces to another library. Instead, use ILLUMOS_0.1 for a new library, and ILLUMOS_m.n for an existing library (where m.n is the next version; i.e., if the last version was ILLUMOS_0.8, then you should version the interfaces with ILLUMOS_0.9). ------------------------------------------------------------------------------- 6.0 Introducing a new library 6.1 Directories The normal discipline for introducing a new library in illumos is to create a new subdirectory of usr/src/lib. The interface definition discipline is to create a common/mapfile-vers file for the new library. If we were introducing a new foo library, libfoo, we'd create usr/src/lib/libfoo containing: Makefile amd64/ i386/ sparcv9/ Makefile.com common/ sparc/ The common subdirectory would contain the normal source files plus the mapfile-vers file. See usr/src/lib/README.Makefiles for directions on how to organize the Makefiles. 6.2 The mapfile The new common/mapfile-vers file would contain: $mapfile_version 2 SYMBOL_VERSION ILLUMOS_0.1 { # first release of libfoo global: ... }; SYMBOL_VERSION ILLUMOSprivate { global: ... local: *; }; If there are no Public interfaces, the ILLUMOS_0.1 section would be omitted. If there are no Private interfaces, the ILLUMOSprivate section would be omitted and the two lines: local: *; would be moved into SUNW_1.1 To decide which interfaces are Public (part of the ABI) and which are Private (unstable interfaces not intended to be used by third parties), the heuristic which works to a first approximation is that if it has a man page then it's Public. For maintainability, the list of names in each version block should be sorted in dictionary order (sort -d). Please comply. ------------------------------------------------------------------------------- 7.0 Make an entire library obsolete 7.1 Introduce SUNWobsolete version Use this version name not for specific interfaces but for marking an entire library as obsolete. The existing public/private version names are left unchanged, but a new SUNWobsolete version is created with no symbols in it. This becomes a tag by which the obsolescence of the library can be recognized. There is no numbering of this version name. $mapfile_version 2 SYMBOL_VERSION SUNWobsolete { global: SUNWobsolete; # This is the only way to do it. } SUNW_1.2; SYMBOL_VERSION ILLUMOS_0.2 { ... You should continue to use the name SUNWobsolete even in illumos. ------------------------------------------------------------------------------- 8.0 Documentation For further information, please refer to the following documents: "Solaris Linker and Libraries Guide"