From: "Richard W.M. Jones" <rjones(a)redhat.com>
---
generator/actions.ml | 1 +
generator/c.ml | 132 +++++++++++++++++++++++++++++++++++++++++++++-----
generator/checks.ml | 10 ++++
generator/structs.ml | 4 +-
generator/structs.mli | 2 +
generator/types.ml | 2 +
po/POTFILES | 1 +
src/Makefile.am | 3 ++
src/compat.c | 26 ++++++++++
src/guestfs.pod | 26 ++++++++++
10 files changed, 195 insertions(+), 12 deletions(-)
create mode 100644 src/compat.c
diff --git a/generator/actions.ml b/generator/actions.ml
index 71aee37..e1db3db 100644
--- a/generator/actions.ml
+++ b/generator/actions.ml
@@ -32,6 +32,7 @@ let defaults = { name = ""; style = RErr, [], []; proc_nr =
None;
progress = false; camel_name = "";
cancellable = false; config_only = false;
once_had_no_optargs = false; blocking = true;
+ symbol_version = None;
c_name = ""; c_function = ""; c_optarg_prefix =
"";
non_c_aliases = [] }
diff --git a/generator/c.ml b/generator/c.ml
index 54f35b5..5e75be1 100644
--- a/generator/c.ml
+++ b/generator/c.ml
@@ -710,7 +710,8 @@ and generate_client_free_structs () =
pr "\n";
List.iter (
- fun { s_name = typ } ->
+ function
+ | { s_name = typ; s_symbol_version = None } ->
pr "GUESTFS_DLL_PUBLIC void\n";
pr "guestfs_free_%s (struct guestfs_%s *x)\n" typ typ;
pr "{\n";
@@ -727,7 +728,38 @@ and generate_client_free_structs () =
pr "}\n";
pr "\n";
- ) structs
+ | { s_name = typ; s_symbol_version = Some v } ->
+ let suffix = "__" ^ replace_char v '.' '_' in
+
+ pr "extern GUESTFS_DLL_PUBLIC void guestfs_free_%s%s (struct guestfs_%s
*x);\n"
+ typ suffix typ;
+ pr "\n";
+ pr "GUESTFS_DLL_PUBLIC void\n";
+ pr "guestfs_free_%s%s (struct guestfs_%s *x)\n" typ suffix typ;
+ pr "{\n";
+ pr " xdr_free ((xdrproc_t) xdr_guestfs_int_%s, (char *) x);\n" typ;
+ pr " free (x);\n";
+ pr "}\n";
+ pr "\n";
+
+ pr "extern GUESTFS_DLL_PUBLIC void guestfs_free_%s_list%s (struct
guestfs_%s_list *x);\n"
+ typ suffix typ;
+ pr "\n";
+ pr "GUESTFS_DLL_PUBLIC void\n";
+ pr "guestfs_free_%s_list%s (struct guestfs_%s_list *x)\n" typ suffix
typ;
+ pr "{\n";
+ pr " xdr_free ((xdrproc_t) xdr_guestfs_int_%s_list, (char *) x);\n"
typ;
+ pr " free (x);\n";
+ pr "}\n";
+ pr "\n";
+
+ ) structs;
+
+ pr "/* Include backwards compatibility code for old ABIs. */\n";
+ pr "#define COMPAT_STRUCTS 1\n";
+ pr "#include \"compat.c\"\n";
+ pr "\n";
+ pr "/* EOF */\n"
(* Generate the client-side dispatch stubs. *)
and generate_client_actions () =
@@ -1108,18 +1140,53 @@ trace_send_line (guestfs_h *g)
)
in
+ (* GCC symver won't let us just use the external name of the
+ * function if the function has multiple versions. Instead we
+ * have to give the function a unique, non-exported name
+ * ('guestfs_...__<VERSION>').
+ *)
+ let suffix_for_symbol_versions c_name style = function
+ | None -> ""
+ | Some v ->
+ let suffix = "__" ^ replace_char v '.' '_' in
+
+ pr "/* This symbol has multiple versions, resolved by the linker\n";
+ pr " * at run time. This is the latest version used for new code.\n";
+ pr " * See src/compat.c for old compatibility versions.\n";
+ pr " */\n";
+ let _, _, optargs = style in
+ if optargs = [] then
+ generate_prototype ~extern:true ~single_line:true ~newline:true
+ ~handle:"g" ~prefix:"guestfs_" ~suffix
+ ~dll_public:true
+ c_name style
+ else
+ generate_prototype ~extern:true ~single_line:true ~newline:true
+ ~handle:"g" ~prefix:"guestfs_" ~suffix:("_argv" ^
suffix)
+ ~optarg_proto:Argv
+ ~dll_public:true
+ c_name style;
+ pr "\n";
+
+ suffix
+ in
+
(* For non-daemon functions, generate a wrapper around each function. *)
let generate_non_daemon_wrapper { name = name; c_name = c_name;
style = ret, _, optargs as style;
- config_only = config_only } =
+ config_only = config_only;
+ symbol_version = symbol_version } =
+ let suffix = suffix_for_symbol_versions c_name style symbol_version in
+
if optargs = [] then
generate_prototype ~extern:false ~semicolon:false ~newline:true
- ~handle:"g" ~prefix:"guestfs_"
+ ~handle:"g" ~prefix:"guestfs_" ~suffix
~dll_public:true
c_name style
else
generate_prototype ~extern:false ~semicolon:false ~newline:true
- ~handle:"g" ~prefix:"guestfs_" ~suffix:"_argv"
~optarg_proto:Argv
+ ~handle:"g" ~prefix:"guestfs_" ~suffix:("_argv" ^
suffix)
+ ~optarg_proto:Argv
~dll_public:true
c_name style;
pr "{\n";
@@ -1184,21 +1251,24 @@ trace_send_line (guestfs_h *g)
(* Client-side stubs for each function. *)
let generate_daemon_stub { name = name; c_name = c_name;
- style = ret, args, optargs as style } =
+ style = ret, args, optargs as style;
+ symbol_version = symbol_version } =
let errcode =
match errcode_of_ret ret with
| `CannotReturnError -> assert false
| (`ErrorIsMinusOne | `ErrorIsNULL) as e -> e in
+ let suffix = suffix_for_symbol_versions c_name style symbol_version in
+
(* Generate the action stub. *)
if optargs = [] then
generate_prototype ~extern:false ~semicolon:false ~newline:true
- ~handle:"g" ~prefix:"guestfs_"
+ ~handle:"g" ~prefix:"guestfs_" ~suffix
~dll_public:true
c_name style
else
generate_prototype ~extern:false ~semicolon:false ~newline:true
- ~handle:"g" ~prefix:"guestfs_" ~suffix:"_argv"
+ ~handle:"g" ~prefix:"guestfs_" ~suffix:("_argv" ^
suffix)
~optarg_proto:Argv
~dll_public:true
c_name style;
@@ -1599,7 +1669,13 @@ trace_send_line (guestfs_h *g)
| ({ style = _, _, (_::_); once_had_no_optargs = true } as f) ->
generate_va_variants f;
generate_back_compat_wrapper f
- ) all_functions_sorted
+ ) all_functions_sorted;
+
+ pr "/* Include backwards compatibility code for old ABIs. */\n";
+ pr "#define COMPAT_ACTIONS 1\n";
+ pr "#include \"compat.c\"\n";
+ pr "\n";
+ pr "/* EOF */\n"
(* Generate the linker script which controls the visibility of
* symbols in the public ABI and ensures no other symbols get
@@ -1667,14 +1743,48 @@ and generate_linker_script () =
) in
let globals = List.sort compare (globals @ functions @ structs) in
- pr "{\n";
+ pr "GUESTFS_0.0 {\n";
pr " global:\n";
List.iter (pr " %s;\n") globals;
pr "\n";
pr " local:\n";
pr " *;\n";
- pr "};\n"
+ pr "};\n";
+
+ (* Explicitly versioned symbols. *)
+ let h = Hashtbl.create 13 in
+ List.iter (
+ function
+ | { symbol_version = None } -> ()
+ | { name = name; symbol_version = Some ver } ->
+ let names = try Hashtbl.find h ver with Not_found -> [] in
+ Hashtbl.replace h ver (name :: names)
+ ) all_functions;
+ List.iter (
+ function
+ | { s_symbol_version = None } -> ()
+ | { s_name = name; s_symbol_version = Some ver } ->
+ let names = try Hashtbl.find h ver with Not_found -> [] in
+ Hashtbl.replace h ver
+ (sprintf "free_%s" name :: sprintf "free_%s_list" name ::
names)
+ ) Structs.structs;
+ let vers = Hashtbl.fold (fun k _ ks -> k :: ks) h [] in
+ let vers = List.sort compare vers in
+ let prev = ref "GUESTFS_0.0" in
+ List.iter (
+ fun ver ->
+ let names = Hashtbl.find h ver in
+ let names = List.sort compare names in
+
+ pr "\n";
+ pr "%s {\n" ver;
+ pr " global:\n";
+ List.iter (pr " guestfs_%s;\n") names;
+ pr "} %s;\n" !prev;
+
+ prev := ver
+ ) vers
and generate_max_proc_nr () =
pr "%d\n" max_proc_nr
diff --git a/generator/checks.ml b/generator/checks.ml
index 2cccf26..f813fbf 100644
--- a/generator/checks.ml
+++ b/generator/checks.ml
@@ -243,6 +243,16 @@ let () =
| { blocking = true } -> ()
) daemon_functions;
+ (* Check symbol version string is sane. *)
+ List.iter (
+ function
+ | { name = name; symbol_version = Some ver } ->
+ let len = String.length ver in
+ if len < 12 || String.sub ver 0 10 <> "GUESTFS_1." then
+ failwithf "%s: invalid symbol_version (%s)" name ver
+ | { symbol_version = None } -> ()
+ ) all_functions;
+
(* Non-fish functions must have correct camel_name. *)
List.iter (
fun { name = name; camel_name = camel_name } ->
diff --git a/generator/structs.ml b/generator/structs.ml
index 4c8e2ef..499f825 100644
--- a/generator/structs.ml
+++ b/generator/structs.ml
@@ -26,6 +26,7 @@ type struc = {
s_name : string;
s_cols : cols;
s_camel_name : string;
+ s_symbol_version : string option;
}
(* Because we generate extra parsing code for LVM command line tools,
@@ -93,7 +94,8 @@ let lvm_lv_cols = [
"modules", FString;
]
-let defaults = { s_name = ""; s_cols = []; s_camel_name = "" }
+let defaults = { s_name = ""; s_cols = []; s_camel_name = "";
+ s_symbol_version = None }
(* Names and fields in all structures (in RStruct and RStructList)
* that we support.
diff --git a/generator/structs.mli b/generator/structs.mli
index 05df115..4e1044d 100644
--- a/generator/structs.mli
+++ b/generator/structs.mli
@@ -27,6 +27,8 @@ type struc = {
s_name : string; (** Regular name. *)
s_cols : cols; (** Columns. *)
s_camel_name : string; (** Camel-cased name. *)
+ s_symbol_version : string option; (** C symbol version.
+ See guestfs(3)/SYMBOL VERSIONING *)
}
val structs : struc list
diff --git a/generator/types.ml b/generator/types.ml
index cff3c6d..df019e8 100644
--- a/generator/types.ml
+++ b/generator/types.ml
@@ -399,6 +399,8 @@ type action = {
set flags in the handle are marked
non-blocking so that we don't add
machinery in various bindings. *)
+ symbol_version : string option; (* C symbol version.
+ See guestfs(3)/SYMBOL VERSIONING *)
(* "Internal" data attached by the generator at various stages. This
* doesn't need to (and shouldn't) be set when defining actions.
diff --git a/po/POTFILES b/po/POTFILES
index 3f49a05..8cc71fe 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -219,6 +219,7 @@ src/actions.c
src/appliance.c
src/bindtests.c
src/command.c
+src/compat.c
src/dbdump.c
src/errnostring-gperf.c
src/errnostring.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 082c122..c567fe2 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -46,6 +46,7 @@ EXTRA_DIST = \
libguestfs.3 \
libguestfs.pc libguestfs.pc.in \
guestfs.pod \
+ compat.c \
api-support/added \
api-support/README \
api-support/update-from-tarballs.sh
@@ -152,6 +153,8 @@ libguestfs_la_SOURCES = \
proto.c \
libguestfs.syms
+actions.c: compat.c
+
libguestfs_la_LIBADD = \
$(PCRE_LIBS) $(MAGIC_LIBS) \
$(LIBVIRT_LIBS) $(LIBXML2_LIBS) \
diff --git a/src/compat.c b/src/compat.c
new file mode 100644
index 0000000..27ffd05
--- /dev/null
+++ b/src/compat.c
@@ -0,0 +1,26 @@
+/* libguestfs
+ * Copyright (C) 2012 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/* This is used for symbol versioning. See guestfs(3)/SYMBOL VERSIONING.
+ *
+ * XXX Symbol versioning only seems to work if all the related
+ * functions are compiled into a single file, so this file is
+ * #included directly into src/actions.c and src/free-structs.c,
+ * instead of being compiled as a separate unit. The cpp symbols
+ * COMPAT_ACTIONS or COMPAT_STRUCTS are defined as appropriate.
+ */
diff --git a/src/guestfs.pod b/src/guestfs.pod
index 60664e7..04d8b6b 100644
--- a/src/guestfs.pod
+++ b/src/guestfs.pod
@@ -3499,6 +3499,32 @@ into the appliance.
Debugging messages are never translated, since they are intended for
the programmers.
+=head2 SYMBOL VERSIONING
+
+The generator supports symbol versioning. This is used
+B<as a last resort only> when we need to modify an API and we
+cannot possibly make the change ABI compatible. Using symbol
+versioning allows us to get older applications to transparently use a
+compatibility function (preserving ABI) while newly compiled
+applications get the new API.
+
+First, familiarize yourself with symbol versioning by reading the
+relevant sections of the GNU ld documentation and this document by
+Ulrich Drepper:
L<http://www.akkadia.org/drepper/dsohowto.pdf>
+
+The modified API should have a C<symbol_version> added. This has the
+form C<GUESTFS_1.X> where C<1.X> is the first stable version of
+libguestfs where the new, incompatible API will appear.
+
+Next edit C<src/compat.c> and add the relevant C<.symver> directives
+so that old libraries call a translation function that is backwards
+compatible with the old ABI, and to make the new symbol the default
+for newly compiled code. There are examples in this file.
+
+Finally check that old binaries do not crash and still return the same
+data. It's a good idea to add a C<debug> call to the translation
+function so you can be sure it is being called.
+
=head2 SOURCE CODE SUBDIRECTORIES
=over 4
--
1.7.11.4