Richard W.M. Jones
2012-Oct-22 09:45 UTC
[Libguestfs] [PATCH 0/2 NOT WORKING] Symbol versioning
John, This was my attempt to add symbol versioning to the library, letting us break ABI without breaking any existing callers. Unfortunately it doesn't work: - the new versioned symbols are marked local in libguestfs.so - the existing symbols should now have @GUESTFS_0.0 versions, but don't The documentation for this stuff is extremely thin, and I've got a bad case of influenza at the moment. Rich.
Richard W.M. Jones
2012-Oct-22 09:45 UTC
[Libguestfs] [PATCH 1/2] generator: Add symbol versioning.
From: "Richard W.M. Jones" <rjones at redhat.com>
---
generator/actions.ml | 1 +
generator/c.ml | 96 ++++++++++++++++++++++++++++++++++++++++++++++------
generator/checks.ml | 10 ++++++
generator/types.ml | 2 ++
src/Makefile.am | 1 +
src/compat.c | 25 ++++++++++++++
src/guestfs.pod | 26 ++++++++++++++
7 files changed, 151 insertions(+), 10 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 ac8fd5e..68f5f46 100644
--- a/generator/c.ml
+++ b/generator/c.ml
@@ -1069,14 +1069,36 @@ trace_send_line (guestfs_h *g)
(* 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 } + (*
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
+ * ('latest_guestfs_...').
+ *)
+ let prefix = match symbol_version with
+ | None -> "guestfs_"
+ | Some _ -> "latest_guestfs_" in
+ if prefix <> "guestfs_" then (
+ if optargs = [] then
+ generate_prototype ~extern:true ~single_line:true ~newline:true
+ ~handle:"g" ~prefix
+ c_name style
+ else
+ generate_prototype ~extern:true ~single_line:true ~newline:true
+ ~handle:"g" ~prefix
+ ~suffix:"_argv" ~optarg_proto:Argv
+ c_name style
+ );
+
if optargs = [] then
generate_prototype ~extern:false ~semicolon:false ~newline:true
- ~handle:"g" ~prefix:"guestfs_"
+ ~handle:"g" ~prefix
c_name style
else
generate_prototype ~extern:false ~semicolon:false ~newline:true
- ~handle:"g" ~prefix:"guestfs_"
~suffix:"_argv" ~optarg_proto:Argv
+ ~handle:"g" ~prefix
+ ~suffix:"_argv" ~optarg_proto:Argv
c_name style;
pr "{\n";
@@ -1140,20 +1162,43 @@ 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
+ (* 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
+ * ('latest_guestfs_...').
+ *)
+ let prefix = match symbol_version with
+ | None -> "guestfs_"
+ | Some _ -> "latest_guestfs_" in
+ if prefix <> "guestfs_" then (
+ if optargs = [] then
+ generate_prototype ~extern:true ~single_line:true ~newline:true
+ ~handle:"g" ~prefix
+ c_name style
+ else
+ generate_prototype ~extern:true ~single_line:true ~newline:true
+ ~handle:"g" ~prefix
+ ~suffix:"_argv" ~optarg_proto:Argv
+ c_name style
+ );
+
(* Generate the action stub. *)
if optargs = [] then
generate_prototype ~extern:false ~semicolon:false ~newline:true
- ~handle:"g" ~prefix:"guestfs_" c_name style
+ ~handle:"g" ~prefix
+ c_name style
else
generate_prototype ~extern:false ~semicolon:false ~newline:true
- ~handle:"g" ~prefix:"guestfs_"
~suffix:"_argv"
- ~optarg_proto:Argv c_name style;
+ ~handle:"g" ~prefix
+ ~suffix:"_argv" ~optarg_proto:Argv
+ c_name style;
pr "{\n";
@@ -1578,7 +1623,12 @@ 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 "#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
@@ -1646,14 +1696,40 @@ 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
+ | { name = name; symbol_version = Some ver } ->
+ let names = try Hashtbl.find h ver with Not_found -> [] in
+ Hashtbl.replace h ver (name :: names)
+ | { symbol_version = None } -> ()
+ ) all_functions;
+ 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/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/src/Makefile.am b/src/Makefile.am
index 5f80bc1..ce2398f 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -45,6 +45,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
diff --git a/src/compat.c b/src/compat.c
new file mode 100644
index 0000000..119eb7c
--- /dev/null
+++ b/src/compat.c
@@ -0,0 +1,25 @@
+/* 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, instead of being compiled as
+ * a separate unit.
+ */
diff --git a/src/guestfs.pod b/src/guestfs.pod
index 6b119dc..8e3baea 100644
--- a/src/guestfs.pod
+++ b/src/guestfs.pod
@@ -3481,6 +3481,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
Richard W.M. Jones
2012-Oct-22 09:45 UTC
[Libguestfs] [PATCH 2/2] [EXAMPLE ONLY] Add extra field(s) to guestfs_application struct.
From: "Richard W.M. Jones" <rjones at redhat.com>
This breaks the ABI, but by using symbol versioning existing callers
will not be affected. Also this change is source compatible, so
existing code does not need to be modified.
---
generator/actions.ml | 1 +
generator/structs.ml | 5 ++++
src/compat.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++
src/inspect-apps.c | 5 ++++
4 files changed, 80 insertions(+)
diff --git a/generator/actions.ml b/generator/actions.ml
index e1db3db..3b0806a 100644
--- a/generator/actions.ml
+++ b/generator/actions.ml
@@ -1522,6 +1522,7 @@ Please read L<guestfs(3)/INSPECTION> for more
details." };
{ defaults with
name = "inspect_list_applications";
style = RStructList ("applications", "application"),
[Device "root"], [];
+ symbol_version = Some "GUESTFS_1.20";
shortdesc = "get list of applications installed in the operating
system";
longdesc = "\
Return the list of applications installed in the operating system.
diff --git a/generator/structs.ml b/generator/structs.ml
index d62fcc5..c6158af 100644
--- a/generator/structs.ml
+++ b/generator/structs.ml
@@ -183,6 +183,7 @@ let structs = [
"app_epoch", FInt32;
"app_version", FString;
"app_release", FString;
+ "app_arch", FString;
"app_install_path", FString;
"app_trans_path", FString;
"app_publisher", FString;
@@ -190,6 +191,10 @@ let structs = [
"app_source_package", FString;
"app_summary", FString;
"app_description", FString;
+ "app_spare1", FString;
+ "app_spare2", FString;
+ "app_spare3", FString;
+ "app_spare4", FString;
];
(* ISO primary volume descriptor. *)
diff --git a/src/compat.c b/src/compat.c
index 119eb7c..c9cde2b 100644
--- a/src/compat.c
+++ b/src/compat.c
@@ -23,3 +23,72 @@
* #included directly into src/actions.c, instead of being compiled as
* a separate unit.
*/
+
+/* guestfs_inspect_list_applications did not return the C<app_arch>
+ * field in libguestfs < 1.20. We cannot add fields to structs
+ * without breaking compatibility.
+ */
+struct compat118_guestfs_application {
+ char *app_name;
+ char *app_display_name;
+ int32_t app_epoch;
+ char *app_version;
+ char *app_release;
+ char *app_install_path;
+ char *app_trans_path;
+ char *app_publisher;
+ char *app_url;
+ char *app_source_package;
+ char *app_summary;
+ char *app_description;
+};
+
+struct compat118_guestfs_application_list {
+ uint32_t len;
+ struct compat118_guestfs_application *val;
+};
+
+/* Declare a prototype to make GCC happy. */
+struct compat118_guestfs_application_list
*compat118_guestfs_inspect_list_applications (guestfs_h *g, const char *root);
+
+struct compat118_guestfs_application_list *
+compat118_guestfs_inspect_list_applications (guestfs_h *g, const char *root)
+{
+ struct compat118_guestfs_application_list *ret;
+ struct guestfs_application_list *r;
+ size_t i;
+
+ debug (g, "%s: compatibility wrapper invoked", __func__);
+
+ /* Call the new function. */
+ r = guestfs_inspect_list_applications (g, root);
+ if (!r)
+ return NULL;
+
+ /* Translate the structures from the new format to the old format. */
+ ret = safe_malloc (g, sizeof (struct compat118_guestfs_application_list));
+ ret->len = r->len;
+ ret->val + safe_malloc (g, sizeof (struct
compat118_guestfs_application) * r->len);
+ for (i = 0; i < r->len; ++i) {
+ ret->val[i].app_name = r->val[i].app_name;
+ ret->val[i].app_display_name = r->val[i].app_display_name;
+ ret->val[i].app_epoch = r->val[i].app_epoch;
+ ret->val[i].app_version = r->val[i].app_version;
+ ret->val[i].app_release = r->val[i].app_release;
+ ret->val[i].app_install_path = r->val[i].app_install_path;
+ ret->val[i].app_trans_path = r->val[i].app_trans_path;
+ ret->val[i].app_publisher = r->val[i].app_publisher;
+ ret->val[i].app_url = r->val[i].app_url;
+ ret->val[i].app_source_package = r->val[i].app_source_package;
+ ret->val[i].app_summary = r->val[i].app_summary;
+ ret->val[i].app_description = r->val[i].app_description;
+ }
+ free (r->val); /* Must not free the strings. */
+ free (r);
+
+ return ret;
+}
+
+__asm__(".symver
compat118_guestfs_inspect_list_applications,guestfs_inspect_list_applications@");
+__asm__(".symver
latest_guestfs_inspect_list_applications,guestfs_inspect_list_applications@@GUESTFS_1.20");
diff --git a/src/inspect-apps.c b/src/inspect-apps.c
index f65c70a..54c10f4 100644
--- a/src/inspect-apps.c
+++ b/src/inspect-apps.c
@@ -555,6 +555,7 @@ add_application (guestfs_h *g, struct
guestfs_application_list *apps,
apps->val[apps->len-1].app_epoch = epoch;
apps->val[apps->len-1].app_version = safe_strdup (g, version);
apps->val[apps->len-1].app_release = safe_strdup (g, release);
+ apps->val[apps->len-1].app_arch = safe_strdup (g, "");
apps->val[apps->len-1].app_install_path = safe_strdup (g,
install_path);
/* XXX Translated path is not implemented yet. */
apps->val[apps->len-1].app_trans_path = safe_strdup (g, "");
@@ -566,6 +567,10 @@ add_application (guestfs_h *g, struct
guestfs_application_list *apps,
apps->val[apps->len-1].app_source_package = safe_strdup (g,
"");
apps->val[apps->len-1].app_summary = safe_strdup (g, "");
apps->val[apps->len-1].app_description = safe_strdup (g, description);
+ apps->val[apps->len-1].app_spare1 = safe_strdup (g, "");
+ apps->val[apps->len-1].app_spare2 = safe_strdup (g, "");
+ apps->val[apps->len-1].app_spare3 = safe_strdup (g, "");
+ apps->val[apps->len-1].app_spare4 = safe_strdup (g, "");
}
/* Sort applications by name before returning the list. */
--
1.7.11.4
John Eckersberg
2012-Oct-23 01:15 UTC
[Libguestfs] [PATCH 0/2 NOT WORKING] Symbol versioning
"Richard W.M. Jones" <rjones at redhat.com> writes:> John, > > This was my attempt to add symbol versioning to the library, > letting us break ABI without breaking any existing callers. > > Unfortunately it doesn't work: > > - the new versioned symbols are marked local in libguestfs.so > > - the existing symbols should now have @GUESTFS_0.0 versions, > but don't > > The documentation for this stuff is extremely thin, and I've > got a bad case of influenza at the moment. > > Rich.Thanks for posting this. After skimming this I decided I needed a crash course on OCaml, so I spent the majority of today doing just that. I think I've wrapped my head around it well enough (past elisp experience helped!) and I'll be looking at this with a slightly more educated eye in the morning. Good luck fighting the influenza, and hope you feel better!
Maybe Matching Threads
- [PATCH v3 0/5] Add symbol versioning.
- [PATCH v2 0/7] Add symbol versioning (now working).
- [PATCH v2 0/3] New inspect_list_applications2 API
- [PATCH 0/2] New inspect_list_applications2 API
- [PATCH for discussion] lib: update inspect_list_applications to return app_arch