[Libosinfo] [PATCH db-tools 2/2] Add osinfo-db-export tool for packing an osinfo database archive

Daniel P. Berrange berrange at redhat.com
Thu Jun 16 08:20:45 UTC 2016


Add a new osinfo-db-export tool which creates an archive in the
tar (pax) format, with XZ compression and can filling it with the
database files from one of the standard libosinfo database locations,
or a custom directory.

This makes it easy for users to create their own local custom database
content and then distribute and import it on many machines. It will
also be used for creating official release archives of the libosinfo
database from upstream.

 $ osinfo-db-export --verbose  osinfo-db-2016-06-15.tar.xz
 osinfo-db-export: d osinfo-db-2016-06-15/
 osinfo-db-export: d osinfo-db-2016-06-15/datamaps
 osinfo-db-export: r osinfo-db-2016-06-15/datamaps/windows-lang.xml
 osinfo-db-export: r osinfo-db-2016-06-15/datamaps/x11-keyboard.xml
 osinfo-db-export: d osinfo-db-2016-06-15/devices
 osinfo-db-export: r osinfo-db-2016-06-15/devices/qemu-pci.xml
 osinfo-db-export: r osinfo-db-2016-06-15/devices/qemu-ps2.xml
 osinfo-db-export: r osinfo-db-2016-06-15/devices/qemu-usb.xml
 osinfo-db-export: r osinfo-db-2016-06-15/devices/virtio-pci.xml
 osinfo-db-export: r osinfo-db-2016-06-15/devices/xen.xml
 osinfo-db-export: d osinfo-db-2016-06-15/hypervisors
 osinfo-db-export: r osinfo-db-2016-06-15/hypervisors/kvm.xml
 ...snip...

Signed-off-by: Daniel P. Berrange <berrange at redhat.com>
---
 .gitignore                    |   3 +
 mingw-osinfo-db-tools.spec.in |   2 +
 osinfo-db-tools.spec.in       |   2 +
 po/POTFILES.in                |   1 +
 tools/Makefile.am             |  10 +-
 tools/osinfo-db-export.c      | 621 ++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 637 insertions(+), 2 deletions(-)
 create mode 100644 tools/osinfo-db-export.c

diff --git a/.gitignore b/.gitignore
index 297976f..bda3aea 100644
--- a/.gitignore
+++ b/.gitignore
@@ -40,6 +40,9 @@ tools/osinfo-db-validate.1
 tools/osinfo-db-import
 tools/osinfo-db-import.exe
 tools/osinfo-db-import.1
+tools/osinfo-db-export
+tools/osinfo-db-export.exe
+tools/osinfo-db-export.1
 po/.intltool-merge-cache
 po/Makefile.in.in
 po/POTFILES
diff --git a/mingw-osinfo-db-tools.spec.in b/mingw-osinfo-db-tools.spec.in
index f3035f9..870b2e4 100644
--- a/mingw-osinfo-db-tools.spec.in
+++ b/mingw-osinfo-db-tools.spec.in
@@ -74,12 +74,14 @@ rm -rf $RPM_BUILD_ROOT%{mingw64_datadir}/man
 
 %files -n mingw32-osinfo-db-tools
 %doc AUTHORS ChangeLog COPYING.LIB NEWS README
+%{mingw32_bindir}/osinfo-db-export.exe
 %{mingw32_bindir}/osinfo-db-import.exe
 %{mingw32_bindir}/osinfo-db-validate.exe
 %{mingw32_datadir}/locale/*/LC_MESSAGES/osinfo-db-tools.mo
 
 %files -n mingw64-osinfo-db-tools
 %doc AUTHORS ChangeLog COPYING.LIB NEWS README
+%{mingw64_bindir}/osinfo-db-export.exe
 %{mingw64_bindir}/osinfo-db-import.exe
 %{mingw64_bindir}/osinfo-db-validate.exe
 %{mingw64_datadir}/locale/*/LC_MESSAGES/osinfo-db-tools.mo
diff --git a/osinfo-db-tools.spec.in b/osinfo-db-tools.spec.in
index 1295f6e..d125751 100644
--- a/osinfo-db-tools.spec.in
+++ b/osinfo-db-tools.spec.in
@@ -34,8 +34,10 @@ information about operating systems for use with virtualization
 %files -f %{name}.lang
 %defattr(-, root, root)
 %doc AUTHORS ChangeLog COPYING.LIB NEWS README
+%{_bindir}/osinfo-db-export
 %{_bindir}/osinfo-db-import
 %{_bindir}/osinfo-db-validate
+%{_mandir}/man1/osinfo-db-export.1*
 %{_mandir}/man1/osinfo-db-import.1*
 %{_mandir}/man1/osinfo-db-validate.1*
 
diff --git a/po/POTFILES.in b/po/POTFILES.in
index c1e69ef..6688a2d 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -1,2 +1,3 @@
+tools/osinfo-db-export.c
 tools/osinfo-db-import.c
 tools/osinfo-db-validate.c
diff --git a/tools/Makefile.am b/tools/Makefile.am
index 5ade53a..701fbe4 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -10,9 +10,9 @@ AM_CFLAGS = $(GOBJECT_CFLAGS) \
 	    -I$(top_srcdir) \
             $(NULL)
 
-bin_PROGRAMS = osinfo-db-validate osinfo-db-import
+bin_PROGRAMS = osinfo-db-validate osinfo-db-import osinfo-db-export
 
-man1_MANS = osinfo-db-validate.1 osinfo-db-import.1
+man1_MANS = osinfo-db-validate.1 osinfo-db-import.1 osinfo-db-export.1
 
 CLEANFILES = $(man1_MANS)
 
@@ -32,3 +32,9 @@ osinfo_db_import_LDADD = $(GOBJECT_LIBS)	\
 		      $(GIO_LIBS)		\
 		      $(GLIB_LIBS)		\
 		      $(LIBARCHIVE_LIBS)
+
+osinfo_db_export_SOURCES = osinfo-db-export.c
+osinfo_db_export_LDADD = $(GOBJECT_LIBS)	\
+		      $(GIO_LIBS)		\
+		      $(GLIB_LIBS)		\
+		      $(LIBARCHIVE_LIBS)
diff --git a/tools/osinfo-db-export.c b/tools/osinfo-db-export.c
new file mode 100644
index 0000000..ea68645
--- /dev/null
+++ b/tools/osinfo-db-export.c
@@ -0,0 +1,621 @@
+/*
+ * Copyright (C) 2016 Red Hat, Inc
+ *
+ * osinfo-db-export: export a database archive
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Authors:
+ *   Daniel P. Berrange <berrange at redhat.com>
+ */
+
+#include <config.h>
+
+#include <glib.h>
+#include <gio/gio.h>
+#include <locale.h>
+#include <glib/gi18n.h>
+#include <stdlib.h>
+#include <archive.h>
+#include <archive_entry.h>
+
+const char *argv0;
+
+static GFile *osinfo_db_export_get_system_path(const gchar *root)
+{
+    GFile *file;
+    gchar *dbdir;
+    const gchar *path = g_getenv("OSINFO_DATA_DIR");
+    if (!path)
+        path = DATA_DIR "/libosinfo";
+
+    dbdir = g_strdup_printf("%s%s/db", root, path);
+    file = g_file_new_for_path(dbdir);
+    g_free(dbdir);
+    return file;
+}
+
+static GFile *osinfo_db_export_get_local_path(const gchar *root)
+{
+    GFile *file;
+    gchar *dbdir;
+
+    dbdir = g_strdup_printf("%s" SYSCONFDIR "/libosinfo/db", root);
+    file = g_file_new_for_path(dbdir);
+    g_free(dbdir);
+    return file;
+}
+
+static GFile *osinfo_db_export_get_user_path(const gchar *root)
+{
+    GFile *file;
+    gchar *dbdir;
+    const gchar *configdir = g_get_user_config_dir();
+
+    dbdir = g_strdup_printf("%s%s/libosinfo/db", root, configdir);
+    file = g_file_new_for_path(dbdir);
+    g_free(dbdir);
+    return file;
+}
+
+
+static GFile *osinfo_db_export_get_custom_path(const gchar *dir,
+                                               const gchar *root)
+{
+    GFile *file;
+    gchar *dbdir;
+
+    dbdir = g_strdup_printf("%s%s", root, dir);
+    file = g_file_new_for_path(dbdir);
+    g_free(dbdir);
+    return file;
+}
+
+
+static GFile *osinfo_db_export_get_path(const char *root,
+                                        gboolean user,
+                                        gboolean local,
+                                        gboolean system,
+                                        const char *custom)
+{
+    if (custom) {
+        return osinfo_db_export_get_custom_path(custom, root);
+    } else if (user) {
+        return osinfo_db_export_get_user_path(root);
+    } else if (local) {
+        return osinfo_db_export_get_local_path(root);
+    } else if (system) {
+        return osinfo_db_export_get_system_path(root);
+#ifndef WIN32
+    } else if (geteuid() == 0) {
+        return osinfo_db_export_get_local_path(root);
+#endif
+    } else {
+        return osinfo_db_export_get_user_path(root);
+    }
+}
+
+
+static int osinfo_db_export_create_file(const gchar *prefix,
+                                        GFile *file,
+                                        GFileInfo *info,
+                                        GFile *base,
+                                        const gchar *target,
+                                        struct archive *arc,
+                                        gboolean verbose);
+
+
+static int osinfo_db_export_create_reg(GFile *file,
+                                       const gchar *abspath,
+                                       const gchar *target,
+                                       struct archive *arc)
+{
+    GFileInputStream *is = NULL;
+    GError *err = NULL;
+    gchar *buf = NULL;
+    gsize size;
+    int ret = -1;
+    gsize rv;
+
+    is = g_file_read(file, NULL, &err);
+    if (!is) {
+        g_printerr("%s: cannot read file %s: %s\n",
+                   argv0, abspath, err->message);
+        goto cleanup;
+    }
+
+    size = 64 * 1024;
+    buf = g_new0(char, size);
+    while (1) {
+        rv = g_input_stream_read(G_INPUT_STREAM(is),
+                                 buf,
+                                 size,
+                                 NULL,
+                                 &err);
+        if (rv == -1) {
+            g_printerr("%s: cannot read data %s: %s\n",
+                       argv0, abspath, err->message);
+            goto cleanup;
+        }
+
+        if (rv == 0)
+            break;
+
+        if (archive_write_data(arc, buf, rv) < 0) {
+            g_printerr("%s: cannot write archive data for %s to %s: %s\n",
+                       argv0, abspath, target, archive_error_string(arc));
+            goto cleanup;
+        }
+    }
+
+    ret = 0;
+ cleanup:
+    if (err)
+        g_error_free(err);
+    g_object_unref(is);
+    g_free(buf);
+    return ret;
+}
+
+static int osinfo_db_export_create_dir(const gchar *prefix,
+                                       GFile *file,
+                                       GFile *base,
+                                       const gchar *abspath,
+                                       const gchar *target,
+                                       struct archive *arc,
+                                       gboolean verbose)
+{
+    GFileEnumerator *children;
+    GError *err = NULL;
+    int ret = -1;
+
+    children = g_file_enumerate_children(file,
+                                         G_FILE_ATTRIBUTE_STANDARD_NAME
+                                         ","
+                                         G_FILE_ATTRIBUTE_STANDARD_SIZE,
+                                         G_FILE_QUERY_INFO_NONE,
+                                         NULL,
+                                         &err);
+    if (!children) {
+        g_printerr("%s: cannot read directory %s: %s\n",
+                   argv0, abspath, err->message);
+        goto cleanup;
+    }
+
+    while (1) {
+        GFileInfo *childinfo = NULL;
+        GFile *child = NULL;
+
+        childinfo = g_file_enumerator_next_file(children, NULL, &err);
+        if (!childinfo) {
+            if (err) {
+                g_printerr("%s: cannot read directory entry %s: %s\n",
+                           argv0, abspath, err->message);
+                goto cleanup;
+            } else {
+                break;
+            }
+        }
+
+        child = g_file_enumerator_get_child(children, childinfo);
+
+        if (osinfo_db_export_create_file(prefix, child, childinfo, base, target, arc, verbose) < 0) {
+            g_object_unref(child);
+            g_object_unref(childinfo);
+            goto cleanup;
+        }
+
+        g_object_unref(child);
+        g_object_unref(childinfo);
+    }
+
+    ret = 0;
+ cleanup:
+    if (err)
+        g_error_free(err);
+    return ret;
+}
+
+
+static int osinfo_db_export_create_file(const gchar *prefix,
+                                        GFile *file,
+                                        GFileInfo *info,
+                                        GFile *base,
+                                        const gchar *target,
+                                        struct archive *arc,
+                                        gboolean verbose)
+{
+    GFileType type = g_file_query_file_type(file,
+                                            G_FILE_QUERY_INFO_NONE,
+                                            NULL);
+    gchar *abspath, *relpath;
+    int ret = -1;
+    GError *err = NULL;
+    gchar *entpath = NULL;
+    struct archive_entry *entry = NULL;
+
+    abspath = g_file_get_path(file);
+    relpath = g_file_get_relative_path(base, file);
+
+    if (!info) {
+        info = g_file_query_info(file,
+                                 G_FILE_ATTRIBUTE_STANDARD_NAME
+                                 ","
+                                 G_FILE_ATTRIBUTE_STANDARD_SIZE,
+                                 G_FILE_QUERY_INFO_NONE,
+                                 NULL,
+                                 &err);
+    } else {
+        g_object_ref(info);
+    }
+    if (!info) {
+        g_printerr("%s: cannot get file info %s: %s\n",
+                   argv0, abspath, err->message);
+        goto cleanup;
+    }
+
+    entpath = g_strdup_printf("%s/%s", prefix, relpath ? relpath : "");
+
+    entry = archive_entry_new();
+    archive_entry_set_pathname(entry, entpath);
+
+    switch (type) {
+    case G_FILE_TYPE_REGULAR:
+    case G_FILE_TYPE_SYMBOLIC_LINK:
+        if (verbose) {
+            g_print("%s: r %s\n", argv0, entpath);
+        }
+        archive_entry_set_filetype(entry, AE_IFREG);
+        archive_entry_set_perm(entry, 0755);
+        archive_entry_set_size(entry, g_file_info_get_size(info));
+        break;
+
+    case G_FILE_TYPE_DIRECTORY:
+        if (verbose) {
+            g_print("%s: d %s\n", argv0, entpath);
+        }
+        archive_entry_set_filetype(entry, AE_IFDIR);
+        archive_entry_set_perm(entry, 0644);
+        archive_entry_set_size(entry, 0);
+        break;
+
+    case G_FILE_TYPE_SPECIAL:
+        g_printerr("%s: cannot archive special file type %s\n",
+                   argv0, abspath);
+        goto cleanup;
+
+    case G_FILE_TYPE_SHORTCUT:
+        g_printerr("%s: cannot archive shortcut file type %s\n",
+                   argv0, abspath);
+        goto cleanup;
+
+    case G_FILE_TYPE_MOUNTABLE:
+        g_printerr("%s: cannot archive mount file type %s\n",
+                   argv0, abspath);
+        goto cleanup;
+
+    case G_FILE_TYPE_UNKNOWN:
+    default:
+        g_printerr("%s: cannot archive unknown file type %s\n",
+                   argv0, abspath);
+        goto cleanup;
+    }
+
+    if (archive_write_header(arc, entry) != ARCHIVE_OK) {
+        g_printerr("%s: cannot write archive header %s: %s\n",
+                   argv0, target, archive_error_string(arc));
+        goto cleanup;
+    }
+
+    switch (type) {
+    case G_FILE_TYPE_REGULAR:
+    case G_FILE_TYPE_SYMBOLIC_LINK:
+        if (osinfo_db_export_create_reg(file, abspath, target, arc) < 0)
+            goto cleanup;
+        break;
+
+    case G_FILE_TYPE_DIRECTORY:
+        if (osinfo_db_export_create_dir(prefix, file, base, abspath, target, arc, verbose) < 0)
+            goto cleanup;
+        break;
+
+    default:
+        g_assert_not_reached();
+    }
+
+    ret = 0;
+ cleanup:
+    archive_entry_free(entry);
+    g_free(abspath);
+    g_free(relpath);
+    g_free(entpath);
+    g_object_unref(info);
+    if (err)
+        g_error_free(err);
+    return ret;
+}
+
+static int osinfo_db_export_create(const gchar *prefix,
+                                   GFile *source,
+                                   const gchar *target,
+                                   gboolean verbose)
+{
+    struct archive *arc;
+    int ret = -1;
+    int r;
+    GError *err = NULL;
+
+    arc = archive_write_new();
+
+    archive_write_add_filter_xz(arc);
+    archive_write_set_format_pax(arc);
+
+    if (target != NULL && g_str_equal(target, "-"))
+        target = NULL;
+
+    if ((r = archive_write_open_filename(arc, target)) != ARCHIVE_OK) {
+        g_printerr("%s: cannot open archive %s: %s\n",
+                   argv0, target, archive_error_string(arc));
+        goto cleanup;
+    }
+
+    if (osinfo_db_export_create_file(prefix, source, NULL, source, target, arc, verbose) < 0) {
+        goto cleanup;
+    }
+
+    if (archive_write_close(arc) != ARCHIVE_OK) {
+        g_printerr("%s: cannot finish writing archive %s: %s\n",
+                   argv0, target, archive_error_string(arc));
+        goto cleanup;
+    }
+
+    ret = 0;
+ cleanup:
+    archive_write_free(arc);
+    if (err)
+        g_error_free(err);
+    return ret;
+}
+
+static gchar *osinfo_db_export_prefix(const gchar *version)
+{
+    if (version == NULL) {
+        GTimeZone *tz = g_time_zone_new_utc();
+        GDateTime *now = g_date_time_new_now(tz);
+        gchar *ret;
+
+        ret = g_strdup_printf("osinfo-db-%04d-%02d-%02d",
+                              g_date_time_get_year(now),
+                              g_date_time_get_month(now),
+                              g_date_time_get_day_of_month(now));
+        g_date_time_unref(now);
+        g_time_zone_unref(tz);
+        return ret;
+    } else {
+        return g_strdup_printf("osinfo-db-%s", version);
+    }
+}
+
+
+gint main(gint argc, gchar **argv)
+{
+    GOptionContext *context;
+    GError *error = NULL;
+    gint ret = EXIT_FAILURE;
+    gboolean verbose = FALSE;
+    gboolean user = FALSE;
+    gboolean local = FALSE;
+    gboolean system = FALSE;
+    const gchar *root = "";
+    const gchar *archive = NULL;
+    const gchar *custom = NULL;
+    const gchar *version = NULL;
+    gchar *prefix = NULL;
+    int locs = 0;
+    GFile *dir = NULL;
+    const GOptionEntry entries[] = {
+      { "verbose", 'v', 0, G_OPTION_ARG_NONE, (void*)&verbose,
+        N_("Verbose progress information"), NULL, },
+      { "user", 0, 0, G_OPTION_ARG_NONE, (void *)&user,
+        N_("Install into user directory"), NULL, },
+      { "local", 0, 0, G_OPTION_ARG_NONE, (void *)&local,
+        N_("Install into local directory"), NULL, },
+      { "system", 0, 0, G_OPTION_ARG_NONE, (void *)&system,
+        N_("Install into system directory"), NULL, },
+      { "dir", 0, 0, G_OPTION_ARG_STRING, (void *)&custom,
+        N_("Import into custom directory"), NULL, },
+      { "version", 0, 0, G_OPTION_ARG_STRING, (void *)&version,
+        N_("Set version number of archive"), NULL, },
+      { "root", 0, 0, G_OPTION_ARG_STRING, &root,
+        N_("Installation root directory"), NULL, },
+      { NULL, 0, 0, 0, NULL, NULL, NULL },
+    };
+    argv0 = argv[0];
+
+    setlocale(LC_ALL, "");
+    textdomain(GETTEXT_PACKAGE);
+    bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
+    bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
+
+    context = g_option_context_new(_("- Validate XML documents "));
+
+    g_option_context_add_main_entries(context, entries, GETTEXT_PACKAGE);
+
+    if (!g_option_context_parse(context, &argc, &argv, &error)) {
+        g_printerr(_("%s: error while parsing commandline options: %s\n\n"),
+                   argv0, error->message);
+        g_printerr("%s\n", g_option_context_get_help(context, FALSE, NULL));
+        goto error;
+    }
+
+    if (argc > 2) {
+        g_printerr(_("%s: expected path to one archive file to export\n"),
+                   argv0);
+        goto error;
+    }
+
+    if (local)
+        locs++;
+    if (system)
+        locs++;
+    if (user)
+        locs++;
+    if (custom)
+        locs++;
+    if (locs > 1) {
+        g_printerr(_("Only one of --user, --local, --system & --dir can be used"));
+        goto error;
+    }
+
+    prefix = osinfo_db_export_prefix(version);
+    archive = argc == 2 ? argv[1] : NULL;
+    dir = osinfo_db_export_get_path(root, user, local, system, custom);
+    if (osinfo_db_export_create(prefix, dir, archive, verbose) < 0)
+        goto error;
+
+    ret = EXIT_SUCCESS;
+
+ error:
+    g_free(prefix);
+    if (dir) {
+        g_object_unref(dir);
+    }
+    g_clear_error(&error);
+    g_option_context_free(context);
+
+    return ret;
+}
+
+
+/*
+=pod
+
+=head1 NAME
+
+osinfo-db-export - Export to a osinfo database archive
+
+=head1 SYNOPSIS
+
+osinfo-db-export [OPTIONS...] ARCHIVE-FILE
+
+=head1 DESCRIPTION
+
+The B<osinfo-db-export> tool will create an osinfo database
+archive file containing the content from one of the standard
+local database locations:
+
+=over 1
+
+=item B<system>
+
+This is the primary system-wide database location, intended
+for use by operating system vendors distributing database
+files in the native package format.
+
+=item B<local>
+
+This is the secondary system-wide database location, intended
+for use by system administrators wishing to provide an updated
+database for all users.
+
+=item B<user>
+
+This is the user private database location, intended for use
+by unprivileged local users wishing to provide applications
+they use with an updated database.
+
+=back
+
+If run by a privileged account (ie root), the B<local> database
+location will be used by default, otherwise the B<user> location
+will be used.
+
+=head1 OPTIONS
+
+=over 8
+
+=item B<--user>
+
+Override the default behaviour to force archiving files from the
+B<user> database location.
+
+=item B<--local>
+
+Override the default behaviour to force archiving files from the
+B<local> database location.
+
+=item B<--system>
+
+Override the default behaviour to force archiving files from the
+B<system> database location.
+
+=item B<--dir=PATH>
+
+Override the default behaviour to force archiving files from the
+custom directory B<PATH>.
+
+=item B<--root=PATH>
+
+Prefix the database location with the root directory given by
+C<PATH>. This is useful when wishing to archive files that are
+in a chroot environment or equivalent.
+
+=item B<--version=VERSION>
+
+Set the version string for the files in the archive to
+B<VERSION>. If this argument is not given, the version
+will be set to the current date in the format B<YYYY-MM-DD>.
+
+=item B<-v>, B<--verbose>
+
+Display verbose progress information when archiving files
+
+=back
+
+=head1 EXIT STATUS
+
+The exit status will be 0 if all files were packed
+successfully, or 1 if at least one file could not be
+packed into the archive.
+
+=head1 SEE ALSO
+
+C<osinfo-db-import(1)>
+
+=head1 AUTHORS
+
+Daniel P. Berrange <berrange at redhat.com>
+
+=head1 COPYRIGHT
+
+Copyright (C) 2016 Red Hat, Inc.
+
+=head1 LICENSE
+
+C<osinfo-db-export> is distributed under the termsof the GNU LGPL v2+
+license. This is free software; see the source for copying conditions.
+There is NO warranty; not even for MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE
+
+=cut
+*/
+
+/*
+ * Local variables:
+ *  indent-tabs-mode: nil
+ *  c-indent-level: 4
+ *  c-basic-offset: 4
+ * End:
+ */
-- 
2.5.5




More information about the Libosinfo mailing list