lib: Expose errno through new API guestfs_last_errno.
authorRichard W.M. Jones <rjones@redhat.com>
Wed, 3 Nov 2010 15:49:36 +0000 (15:49 +0000)
committerRichard W.M. Jones <rjones@redhat.com>
Wed, 3 Nov 2010 18:48:56 +0000 (18:48 +0000)
If either the daemon sends back an errno, or a system call
fails in the library, save the errno in the handle and then
make it available to callers through the guestfs_last_errno
function.

14 files changed:
.gitignore
README
configure.ac
daemon/Makefile.am
daemon/configure.ac
generator/generator_c.ml
generator/generator_errnostring.ml
generator/generator_main.ml
po/POTFILES.in
src/Makefile.am
src/guestfs-internal.h
src/guestfs.c
src/guestfs.h
src/guestfs.pod

index e5c9ffa..14060ad 100644 (file)
@@ -46,6 +46,8 @@ configure
 cscope.out
 csharp/
 daemon/actions.h
+daemon/errnostring_gperf.c
+daemon/errnostring_gperf.gperf
 daemon/errnostring.c
 daemon/errnostring.h
 daemon/guestfsd
@@ -252,6 +254,8 @@ ruby/ext/guestfs/mkmf.log
 ruby/Rakefile
 src/actions.c
 src/bindtests.c
+src/errnostring_gperf.c
+src/errnostring_gperf.gperf
 src/errnostring.c
 src/errnostring.h
 src/guestfs-actions.h
diff --git a/README b/README
index a88f4cc..07915e4 100644 (file)
--- a/README
+++ b/README
@@ -55,6 +55,8 @@ Requirements
 
 - libxml2
 
+- gperf
+
 - Augeas (http://augeas.net/)
 
 - squashfs-tools (mksquashfs only)
index 1abeb6b..e2a6b91 100644 (file)
@@ -209,6 +209,11 @@ AC_CHECK_PROG([CPIO],[cpio],[cpio],[no])
 test "x$CPIO" = "xno" &&
      AC_MSG_ERROR([cpio must be installed])
 
+dnl Check for gperf.
+AC_CHECK_PROG([GPERF],[gperf],[gperf],[no])
+test "x$GPERF" = "xno" &&
+     AC_MSG_ERROR([gperf must be installed])
+
 dnl Check for pod2man and pod2text.
 AC_CHECK_PROG([POD2MAN],[pod2man],[pod2man],[no])
 test "x$POD2MAN" = "xno" &&
index 2b585c2..ee1959f 100644 (file)
@@ -37,6 +37,8 @@ BUILT_SOURCES = \
        $(generator_built) \
        guestfs_protocol.c \
        guestfs_protocol.h \
+       errnostring_gperf.c \
+       errnostring_gperf.gperf \
        errnostring.c \
        errnostring.h
 
@@ -62,6 +64,22 @@ $(libsrcdir)/guestfs_protocol.c: force
 $(libsrcdir)/guestfs_protocol.h: force
        $(MAKE) -C $(libsrcdir) guestfs_protocol.h
 
+# Build the errnostring perfect hash code.  The generated code has lots
+# of warnings so we must compile it in a separate mini-library.
+noinst_LIBRARIES += liberrnostring.a
+liberrnostring_a_SOURCES = \
+       errnostring_gperf.c \
+       errnostring.h \
+       errnostring.c
+liberrnostring_a_CFLAGS =
+
+errnostring_gperf.c: errnostring_gperf.gperf
+       rm -f $@
+       $(GPERF) -t $< > $@-t
+       mv $@-t $@
+errnostring_gperf.gperf: $(libsrcdir)/errnostring_gperf.gperf
+       rm -f $@
+       ln $< $@
 errnostring.c: $(libsrcdir)/errnostring.c
        rm -f $@
        ln $< $@
@@ -91,8 +109,6 @@ guestfsd_SOURCES = \
        dropcaches.c \
        du.c \
        echo_daemon.c \
-       errnostring.h \
-       errnostring.c \
        ext2.c \
        fallocate.c \
        file.c \
@@ -148,6 +164,7 @@ guestfsd_SOURCES = \
        zero.c \
        zerofree.c
 guestfsd_LDADD = \
+       liberrnostring.a \
        libprotocol.a \
        lib/libgnu.a \
        $(GETADDRINFO_LIB) \
index 27bb997..2be84a8 100644 (file)
@@ -137,6 +137,11 @@ AC_STRUCT_ST_BLOCKS
 AC_CHECK_MEMBER([struct stat.st_blksize],[
         AC_DEFINE([HAVE_STRUCT_STAT_ST_BLKSIZE],[1],[Define to 1 if 'st_blksize' is a member of 'struct stat'])])
 
+dnl Check for gperf.
+AC_CHECK_PROG([GPERF],[gperf],[gperf],[no])
+test "x$GPERF" = "xno" &&
+     AC_MSG_ERROR([gperf must be installed])
+
 dnl Check for Augeas (now optional).
 AC_CHECK_LIB([augeas],[aug_match],[
         LIBS="-laugeas $LIBS"
index 513bc1d..c1b2e63 100644 (file)
@@ -438,6 +438,7 @@ and generate_client_actions () =
 #include \"guestfs-internal.h\"
 #include \"guestfs-internal-actions.h\"
 #include \"guestfs_protocol.h\"
+#include \"errnostring.h\"
 
 /* Check the return message from a call for validity. */
 static int
@@ -802,7 +803,16 @@ check_state (guestfs_h *g, const char *caller)
       pr "\n";
 
       pr "  if (hdr.status == GUESTFS_STATUS_ERROR) {\n";
-      pr "    error (g, \"%%s: %%s\", \"%s\", err.error_message);\n" shortname;
+      pr "    int errnum = 0;\n";
+      pr "    if (err.errno_string[0] != '\\0')\n";
+      pr "      errnum = guestfs___string_to_errno (err.errno_string);\n";
+      pr "    if (errnum <= 0)\n";
+      pr "      error (g, \"%%s: %%s\", \"%s\", err.error_message);\n"
+        shortname;
+      pr "    else\n";
+      pr "      guestfs_error_errno (g, errnum, \"%%s: %%s\", \"%s\",\n"
+        shortname;
+      pr "                           err.error_message);\n";
       pr "    free (err.error_message);\n";
       pr "    free (err.errno_string);\n";
       pr "    guestfs___end_busy (g);\n";
@@ -995,6 +1005,7 @@ and generate_linker_script () =
     "guestfs_get_error_handler";
     "guestfs_get_out_of_memory_handler";
     "guestfs_get_private";
+    "guestfs_last_errno";
     "guestfs_last_error";
     "guestfs_set_close_callback";
     "guestfs_set_error_handler";
index d0a5734..a024989 100644 (file)
@@ -224,7 +224,11 @@ extern const char *guestfs___errno_to_string (int errnum);
  * system, EINVAL is returned (all POSIX-conforming systems must
  * support EINVAL).
  */
-//extern int guestfs___string_to_errno (const char *errnostr);
+extern int guestfs___string_to_errno (const char *errnostr);
+
+/* Private structure and function used by the perfect hash implementation. */
+struct errnostring_entry { char *name; int errnum; };
+extern const struct errnostring_entry *guestfs___string_to_errno_lookup (register const char *str, register unsigned int len);
 
 #endif /* GUESTFS_ERRNOSTRING_H_ */
 "
@@ -236,6 +240,7 @@ let generate_errnostring_c () =
 #include <config.h>
 
 #include <stdlib.h>
+#include <string.h>
 #include <errno.h>
 
 #include \"errnostring.h\"
@@ -270,4 +275,63 @@ guestfs___errno_to_string (int errnum)
     return errno_to_string[errnum];
 }
 
+int
+guestfs___string_to_errno (const char *errnostr)
+{
+  const struct errnostring_entry *v =
+    guestfs___string_to_errno_lookup (errnostr, strlen (errnostr));
+  if (v /* not necessary to check v->name != NULL here */)
+    return v->errnum;
+  else
+    return EINVAL;
+}
 "
+
+let generate_errnostring_gperf () =
+  generate_header CStyle LGPLv2plus;
+
+  pr "\
+%%language=ANSI-C
+%%define lookup-function-name guestfs___string_to_errno_lookup
+%%readonly-tables
+%%null-strings
+
+%%{
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include \"errnostring.h\"
+
+";
+
+  (* Some of these errnos might not exist on the target platform, but
+   * we are going to include E_ macros directly in the C output of
+   * gperf.  To avoid this causing errors, we include macros to define
+   * unknown errors as EINVAL (see specification of
+   * guestfs___string_to_errno above).  Note this only affects the
+   * single output file containing gperf-generated code.
+   *)
+  List.iter (
+    fun e ->
+      pr "#ifndef %s\n" e;
+      pr "#define %s EINVAL\n" e;
+      pr "#endif\n";
+  ) errnos;
+
+  pr "\
+
+%%}
+
+struct errnostring_entry;
+
+%%%%
+";
+
+  List.iter (
+    fun e ->
+      pr "%s, %s\n" e e
+  ) errnos
index 1aa15d0..2d4b241 100644 (file)
@@ -81,6 +81,7 @@ Run it from the top source directory using the command
   output_to "src/guestfs-structs.pod" generate_structs_pod;
   output_to "src/guestfs-actions.pod" generate_actions_pod;
   output_to "src/guestfs-availability.pod" generate_availability_pod;
+  output_to "src/errnostring_gperf.gperf" generate_errnostring_gperf;
   output_to "src/errnostring.c" generate_errnostring_c;
   output_to "src/errnostring.h" generate_errnostring_h;
   output_to "src/MAX_PROC_NR" generate_max_proc_nr;
index 17b98b4..5d4bdf7 100644 (file)
@@ -17,6 +17,7 @@ daemon/dropcaches.c
 daemon/du.c
 daemon/echo_daemon.c
 daemon/errnostring.c
+daemon/errnostring_gperf.c
 daemon/ext2.c
 daemon/fallocate.c
 daemon/file.c
@@ -120,6 +121,7 @@ src/actions.c
 src/appliance.c
 src/bindtests.c
 src/errnostring.c
+src/errnostring_gperf.c
 src/guestfs.c
 src/inspect.c
 src/launch.c
index 4c5468b..4bcf7e9 100644 (file)
@@ -24,15 +24,19 @@ generator_built = \
        guestfs-internal-actions.h \
        actions.c \
        bindtests.c \
+       errnostring_gperf.gperf \
+       errnostring.c \
+       errnostring.h
        guestfs-actions.pod \
        guestfs-availability.pod \
        guestfs-structs.pod \
        libguestfs.syms
 
 BUILT_SOURCES = \
-  $(generator_built) \
-  guestfs_protocol.c \
-  guestfs_protocol.h
+       $(generator_built) \
+       guestfs_protocol.c \
+       guestfs_protocol.h \
+       errnostring_gperf.c
 
 EXTRA_DIST = \
        $(BUILT_SOURCES) \
@@ -52,6 +56,19 @@ libprotocol_la_SOURCES = \
 
 libprotocol_la_CFLAGS =
 
+# Build the errnostring perfect hash code.  The generated code has lots
+# of warnings so we must compile it in a separate mini-library.
+liberrnostring_la_SOURCES = \
+       errnostring_gperf.c \
+       errnostring.h \
+       errnostring.c
+liberrnostring_la_CFLAGS =
+
+errnostring_gperf.c: errnostring_gperf.gperf
+       rm -f $@
+       $(GPERF) -t $< > $@-t
+       mv $@-t $@
+
 # From the libtool info file, with comments:
 #
 # |  1. Start with version information of `0:0:0' for each libtool library.
@@ -115,9 +132,9 @@ libguestfs_la_SOURCES = \
 
 libguestfs_la_LIBADD = $(HIVEX_LIBS) $(AUGEAS_LIBS) $(LIBPCRE) $(LIBMAGIC) $(LTLIBTHREAD) ../gnulib/lib/libgnu.la
 
-# Make libguestfs include the convenience library.
-noinst_LTLIBRARIES = libprotocol.la
-libguestfs_la_LIBADD += libprotocol.la
+# Make libguestfs include the convenience libraries.
+noinst_LTLIBRARIES = liberrnostring.la libprotocol.la
+libguestfs_la_LIBADD += liberrnostring.la libprotocol.la
 
 libguestfs_la_CFLAGS = \
   -DGUESTFS_DEFAULT_PATH='"$(libdir)/guestfs"' \
index ffd8cf1..9c7c96c 100644 (file)
@@ -114,6 +114,7 @@ struct guestfs_h
   int selinux;                  /* selinux enabled? */
 
   char *last_error;
+  int last_errnum;              /* errno, or 0 if there was no errno */
 
   /* Callbacks. */
   guestfs_abort_cb           abort_cb;
@@ -200,6 +201,8 @@ struct guestfs_message_error;
 
 extern void guestfs_error (guestfs_h *g, const char *fs, ...)
   __attribute__((format (printf,2,3)));
+extern void guestfs_error_errno (guestfs_h *g, int errnum, const char *fs, ...)
+  __attribute__((format (printf,3,4)));
 extern void guestfs_perrorf (guestfs_h *g, const char *fs, ...)
   __attribute__((format (printf,2,3)));
 extern void *guestfs_safe_realloc (guestfs_h *g, void *ptr, int nbytes);
@@ -221,7 +224,7 @@ extern int guestfs___accept_from_daemon (guestfs_h *g);
 extern int guestfs___build_appliance (guestfs_h *g, char **kernel, char **initrd, char **appliance);
 extern void guestfs___print_BufferIn (FILE *out, const char *buf, size_t buf_size);
 
-#define error guestfs_error
+#define error(g,...) guestfs_error_errno((g),0,__VA_ARGS__)
 #define perrorf guestfs_perrorf
 #define safe_calloc guestfs_safe_calloc
 #define safe_malloc guestfs_safe_malloc
index df13d51..e4f74e0 100644 (file)
@@ -270,11 +270,18 @@ guestfs_last_error (guestfs_h *g)
   return g->last_error;
 }
 
+int
+guestfs_last_errno (guestfs_h *g)
+{
+  return g->last_errnum;
+}
+
 static void
-set_last_error (guestfs_h *g, const char *msg)
+set_last_error (guestfs_h *g, int errnum, const char *msg)
 {
   free (g->last_error);
   g->last_error = strdup (msg);
+  g->last_errnum = errnum;
 }
 
 static void
@@ -284,7 +291,7 @@ default_error_cb (guestfs_h *g, void *data, const char *msg)
 }
 
 void
-guestfs_error (guestfs_h *g, const char *fs, ...)
+guestfs_error_errno (guestfs_h *g, int errnum, const char *fs, ...)
 {
   va_list args;
   char *msg;
@@ -295,8 +302,11 @@ guestfs_error (guestfs_h *g, const char *fs, ...)
 
   if (err < 0) return;
 
+  /* set_last_error first so that the callback can access the error
+   * message and errno through the handle if it wishes.
+   */
+  set_last_error (g, errnum, msg);
   if (g->error_cb) g->error_cb (g, g->error_cb_data, msg);
-  set_last_error (g, msg);
 
   free (msg);
 }
@@ -327,8 +337,11 @@ guestfs_perrorf (guestfs_h *g, const char *fs, ...)
   strcat (msg, ": ");
   strcat (msg, buf);
 
+  /* set_last_error first so that the callback can access the error
+   * message and errno through the handle if it wishes.
+   */
+  set_last_error (g, errnum, msg);
   if (g->error_cb) g->error_cb (g, g->error_cb_data, msg);
-  set_last_error (g, msg);
 
   free (msg);
 }
index 911e956..977b4c5 100644 (file)
@@ -44,6 +44,8 @@ extern void guestfs_close (guestfs_h *g);
 
 /*--- Error handling ---*/
 extern const char *guestfs_last_error (guestfs_h *g);
+#define LIBGUESTFS_HAVE_LAST_ERRNO 1
+extern int guestfs_last_errno (guestfs_h *g);
 
 typedef void (*guestfs_error_handler_cb) (guestfs_h *g, void *opaque, const char *msg);
 typedef void (*guestfs_abort_cb) (void) __attribute__((__noreturn__));
index 603fe20..da03128 100644 (file)
@@ -770,20 +770,6 @@ The error message you get from this is also a little obscure.
 This could be fixed in the generator by specially marking parameters
 and return values which take bytes or other units.
 
-=item Library should return errno with error messages.
-
-It would be a nice-to-have to be able to get the original value of
-'errno' from inside the appliance along error paths (where set).
-Currently L<guestmount(1)> goes through hoops to try to reverse the
-error message string into an errno, see the function error() in
-fuse/guestmount.c.
-
-In libguestfs 1.5.4, the protocol was changed so that the
-Linux errno is sent back from the daemon.
-
-In libguestfs 1.7.1, the protocol was changed again to send the
-errno back as a string (for portability between differing Un*xes).
-
 =item Ambiguity between devices and paths
 
 There is a subtle ambiguity in the API between a device name
@@ -892,9 +878,16 @@ This closes the connection handle and frees up all resources used.
 =head1 ERROR HANDLING
 
 The convention in all functions that return C<int> is that they return
-C<-1> to indicate an error.  You can get additional information on
-errors by calling L</guestfs_last_error> and/or by setting up an error
-handler with L</guestfs_set_error_handler>.
+C<-1> to indicate an error.
+
+Additional information is available for errors: an error message
+string and optionally an error number (errno) if the thing that failed
+was a system call.
+
+You can get at the additional information about the last error on the
+handle by calling L</guestfs_last_error>, L</guestfs_last_errno>,
+and/or by setting up an error handler with
+L</guestfs_set_error_handler>.
 
 The default error handler prints the information string to C<stderr>.
 
@@ -917,6 +910,45 @@ The error string is not localized (ie. is always in English), because
 this makes searching for error messages in search engines give the
 largest number of results.
 
+=head2 guestfs_last_errno
+
+ int guestfs_last_errno (guestfs_h *g);
+
+This returns the last error number (errno) that happened on C<g>.
+
+If successful, an errno integer not equal to zero is returned.
+
+If no error, this returns 0.  This call can return 0 in three
+situations:
+
+=over 4
+
+=item 1.
+
+There has not been any error on the handle.
+
+=item 2.
+
+There has been an error but the errno was meaningless.  This
+corresponds to the case where the error did not come from a
+failed system call, but for some other reason.
+
+=item 3.
+
+There was an error from a failed system call, but for some
+reason the errno was not captured and returned.  This usually
+indicates a bug in libguestfs.
+
+=back
+
+Libguestfs tries to convert the errno from inside the applicance into
+a corresponding errno for the caller (not entirely trivial: the
+appliance might be running a completely different operating system
+from the library and error numbers are not standardized across
+Un*xen).  If this could not be done, then the error is translated to
+C<EINVAL>.  In practice this should only happen in very rare
+circumstances.
+
 =head2 guestfs_set_error_handler
 
  typedef void (*guestfs_error_handler_cb) (guestfs_h *g,
@@ -930,6 +962,9 @@ The callback C<cb> will be called if there is an error.  The
 parameters passed to the callback are an opaque data pointer and the
 error message string.
 
+C<errno> is not passed to the callback.  To get that the callback must
+call L</guestfs_last_errno>.
+
 Note that the message string C<msg> is freed as soon as the callback
 function returns, so if you want to stash it somewhere you must make
 your own copy.