mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/jaegeuk/f2fs-tools.git
synced 2024-11-27 03:33:35 +08:00
224 lines
9.1 KiB
Plaintext
224 lines
9.1 KiB
Plaintext
|
-------------------
|
||
|
Written by Ted T'so
|
||
|
-------------------
|
||
|
|
||
|
> https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
|
||
|
>
|
||
|
> I understood that, if there is no interface change but some implementation
|
||
|
> changes, I need to bump revision. If new interface is added, for example, I
|
||
|
> need to bump current while revision=0 and age++.
|
||
|
|
||
|
So part of the problem here is that libtool is doing something really
|
||
|
strange because they are trying to use some abstract concept that is
|
||
|
OS-independent. I don't use libtool because I find it horribly
|
||
|
complex and doesn't add enough value to be worth the complexity.
|
||
|
|
||
|
So I'll tell you how things work with respect to Linux's ELF version
|
||
|
numbering system. Translating this to libtool's wierd "current,
|
||
|
revision, age" terminology is left as an exercise to the reader. I've
|
||
|
looked at the libtool documentation, and it confuses me horribly.
|
||
|
Reading it, I suspect it's wrong, but I don't have the time to
|
||
|
experiment to confirm that the documentation is wrong and how it
|
||
|
diverges from the libtool implementation.
|
||
|
|
||
|
So let me explain things using the ELF shared library terminology,
|
||
|
which is "major version, minor version, patchlevel". This shows up in
|
||
|
the library name:
|
||
|
|
||
|
libudev.so.1.6.11
|
||
|
|
||
|
So in this example, the major version number is 1, the minor version
|
||
|
is 6, and the patchlevel is 11. The patchlevel is entirely optional,
|
||
|
and many packages don't use it at all. The minor number is also
|
||
|
mostly useless on Linux, but it's still there for historical reasons.
|
||
|
The patchlevel and minor version numbers were useful back for SunOS
|
||
|
(and Linux a.out shared library), back when there weren't rpm and dpkg
|
||
|
as package managers.
|
||
|
|
||
|
So many modern Linux shared libraries will only use the major and
|
||
|
minor version numbers, e.g:
|
||
|
|
||
|
libext2fs.so.2.4
|
||
|
|
||
|
The only thing you really need to worry about is the major version
|
||
|
number, really. The minor version is *supposed* to change when new
|
||
|
interfaces has changed (but I and most other people don't do that any
|
||
|
more). But the big deal is that the major number *must* get bumped if
|
||
|
an existing interface has *changed*.
|
||
|
|
||
|
So let's talk about the major version number, and then we'll talk
|
||
|
about why the minor version number isn't really a big deal for Linux.
|
||
|
|
||
|
So if you change any of the library's function signatures --- and this
|
||
|
includes changing a type from a 32-bit integer to a 64-bit integer,
|
||
|
that's an ABI breakage, and so you must bump the major version number
|
||
|
so that a program that was linked against libfoo.so.4 doesn't try to
|
||
|
use libfoo.so.5. That's really the key --- will a program linked
|
||
|
against the previous version library break if it links against the
|
||
|
newer version. If it does, then you need to bump the version number.
|
||
|
|
||
|
So for structures, if you change any of the existing fields, or if the
|
||
|
application program allocates the structure --- either by declaring it
|
||
|
on the stack, or via malloc() --- and you expand the structure,
|
||
|
obviously that will cause problem, and so that's an ABI break.
|
||
|
|
||
|
If however, you arrange to have structures allocated by the library,
|
||
|
and struct members are always added at the end, then an older program
|
||
|
won't have any problems. You can guarantee this by simply only using
|
||
|
a pointer to the struct in your public header files, and defining the
|
||
|
struct in a private header file that is not available to userspace
|
||
|
programs.
|
||
|
|
||
|
Similarly, adding new functions never breaks the ABI. That's because
|
||
|
older program won't try to use the newer interfaces. So if I need to
|
||
|
change an interface to a function, what I'll generally do is to define
|
||
|
a new function, and then implement the older function in terms of the
|
||
|
newer one. For example:
|
||
|
|
||
|
extern errcode_t ext2fs_open(const char *name, int flags, int superblock,
|
||
|
unsigned int block_size, io_manager manager,
|
||
|
ext2_filsys *ret_fs);
|
||
|
|
||
|
extern errcode_t ext2fs_open2(const char *name, const char *io_options,
|
||
|
int flags, int superblock,
|
||
|
unsigned int block_size, io_manager manager,
|
||
|
ext2_filsys *hret_fs);
|
||
|
|
||
|
As far as the minor version numbers are concerned, the dynamic linker
|
||
|
doesn't use it. In SunOS 4, if you have a DT_NEEDED for libfoo.so.4,
|
||
|
and the dynamic linker finds in its search path:
|
||
|
|
||
|
libfoo.so.4.8
|
||
|
libfoo.so.4.9
|
||
|
|
||
|
It will preferentially use libfoo.so.4.9.
|
||
|
|
||
|
That's not how it works in Linux, though. In Linux there will be a
|
||
|
symlink that points libfoo.so.4 to libfoo.so.4.9, and the linker just
|
||
|
looks for libfoo.so.4. One could imagine a package manager which
|
||
|
adjusts the symlink to point at the library with the highest version,
|
||
|
but given that libfoo.so.4.9 is supposed to contain a superset of
|
||
|
libfoo.so.4.8, there's no point. So we just in practice handle all of
|
||
|
this in the package manager, or via an ELF symbol map. Or, we just
|
||
|
assume that since vast majority of software comes from the
|
||
|
distribution, the distro package manager will just update libraries to
|
||
|
the newer version as a matter of course, and nothing special needs to
|
||
|
be done.
|
||
|
|
||
|
So in practice I don't bump the minor version number for e2fsprogs
|
||
|
each time I add new interfaces, because in practice it really doesn't
|
||
|
matter for Linux. We have a much better system that gets used for
|
||
|
Debian.
|
||
|
|
||
|
For example in Debian there is a file that contains when each symbol
|
||
|
was first introduced into a library, by its package version number.
|
||
|
See:
|
||
|
|
||
|
https://git.kernel.org/pub/scm/fs/ext2/e2fsprogs.git/tree/debian/libext2fs2.symbols
|
||
|
|
||
|
This file contains a version number for each symbol in libext2fs2, and
|
||
|
it tells us what version of libext2fs you need to guarantee that a
|
||
|
particular symbol is present in the library. Then when *other*
|
||
|
packages are built that depend on libext2fs2, the minimum version of
|
||
|
libext2fs can be calculated based on which symbols they use.
|
||
|
|
||
|
So for example the libf2fs-format4 package has a Debian dependency of:
|
||
|
|
||
|
Depends: libblkid1 (>= 2.17.2), libc6 (>= 2.14), libf2fs5, libuuid1 (>= 2.16)
|
||
|
|
||
|
The minimum version numbers needed for libblkid1 and libuuid1 are
|
||
|
determined by figuring out all of the symbols used by the
|
||
|
libf2fs-format4 package, and determining the minimum version number of
|
||
|
libblkid1 that supports all of those blkid functions.
|
||
|
|
||
|
This gets done automatically, so I didn't have to figure this out.
|
||
|
All I have in the debian/control file is:
|
||
|
|
||
|
Depends: ${misc:Depends}, ${shlibs:Depends}
|
||
|
|
||
|
Sorry this got so long, but hopefully you'll find this useful. How
|
||
|
you bend libtool to your will is something you'll have to figure out,
|
||
|
because I don't use libtool in my packages.[1]
|
||
|
|
||
|
Cheers,
|
||
|
|
||
|
- Ted
|
||
|
|
||
|
|
||
|
[1] If you are interested in how I do things in e2fsprogs, take a look
|
||
|
at the Makefile.elf-lib, Makefile.solaris-lib, Makefile.darwin-lib,
|
||
|
etc. here:
|
||
|
|
||
|
https://git.kernel.org/pub/scm/fs/ext2/e2fsprogs.git/tree/lib
|
||
|
|
||
|
This these Makefile fragments are then pulled into the generated
|
||
|
makefile using autoconf's substitution rules, here:
|
||
|
|
||
|
https://git.kernel.org/pub/scm/fs/ext2/e2fsprogs.git/tree/lib/ext2fs/Makefile.in
|
||
|
|
||
|
(Search for "@MAKEFILE_ELF@" in the above Makefile.in).
|
||
|
|
||
|
So when someone runs "configure --enable-elf-shlibs", they get the ELF
|
||
|
shared libraries built. On BSD and MacOS systems they just have to
|
||
|
run "configure --enable-bsd-shlibs", and so on.
|
||
|
|
||
|
Personally, since most people don't bother to write truly portable
|
||
|
programs, as their C code is full of Linux'isms, using libtool is just
|
||
|
overkill, because they probably can't build on any other OS *anyway*
|
||
|
so libtool's slow and complex abstraction layer is totally wasted.
|
||
|
Might as well not use autoconf, automake, and libtool at all.
|
||
|
|
||
|
On the other hand, if you really *do* worry about portability on other
|
||
|
OS's (e2fsprogs builds on MacOS, NetBSD, Hurd, Solaris, etc.) then
|
||
|
using autoconf makes sense --- but I *still* don't think the
|
||
|
complexity of libtool is worth it.
|
||
|
|
||
|
= Add-on =
|
||
|
If you are going to be making one less major update, this is the
|
||
|
perfect time to make sure that data structures are allocated by the
|
||
|
library, and are (ideally) opaque to the calling application (so they
|
||
|
only manipulate structure poitners). That is, the structure
|
||
|
definition is not exposed in the public header file, and you use
|
||
|
accessor functions to set and get fields in the structure.
|
||
|
|
||
|
If you can't do that for all data structures, if you can do that with
|
||
|
your primary data structure that's going to make your life much easier
|
||
|
in the long term. For ext2fs, that's the file systme handle. It's
|
||
|
created by ext2fs_open(), and it's passed to all other library
|
||
|
functions as the first argument.
|
||
|
|
||
|
The other thing you might want to consider doing is adding a magic
|
||
|
number to the beginning of each structure. That way you can tell if
|
||
|
the wrong structure gets passed to a library. It's also helpful for
|
||
|
doing the equivalent of subclassing in C.
|
||
|
|
||
|
This is how we do it in libext2fs --- we use com_err to define the
|
||
|
magic numbers:
|
||
|
|
||
|
error_table ext2
|
||
|
|
||
|
ec EXT2_ET_BASE,
|
||
|
"EXT2FS Library version @E2FSPROGS_VERSION@"
|
||
|
|
||
|
ec EXT2_ET_MAGIC_EXT2FS_FILSYS,
|
||
|
"Wrong magic number for ext2_filsys structure"
|
||
|
|
||
|
ec EXT2_ET_MAGIC_BADBLOCKS_LIST,
|
||
|
"Wrong magic number for badblocks_list structure"
|
||
|
...
|
||
|
|
||
|
And then every single structure starts like so:
|
||
|
|
||
|
struct struct_ext2_filsys {
|
||
|
errcode_t magic;
|
||
|
...
|
||
|
|
||
|
struct ext2_struct_inode_scan {
|
||
|
errcode_t magic;
|
||
|
...
|
||
|
|
||
|
And then before we use any pointer we do this:
|
||
|
|
||
|
if (file->magic != EXT2_ET_MAGIC_EXT2_FILE)
|
||
|
return EXT2_ET_MAGIC_EXT2_FILE;
|