Actual parsing code taken from libreport, originally written by Jakub
Filak.
Implementing the parser with callback allows reusing it in libreport without
bringing in the libreport-specific hacks and workarounds.
Related to #94.
Signed-off-by: Martin Milata <mmilata(a)redhat.com>
---
include/operating_system.h | 3 ++
include/utils.h | 10 ++++++
lib/abrt.c | 31 ++++++++++++------
lib/operating_system.c | 31 ++++++++++++++++++
lib/utils.c | 79 ++++++++++++++++++++++++++++++++++++++++++++++
tests/operating_system.at | 57 +++++++++++++++++++++++++++++++++
6 files changed, 202 insertions(+), 9 deletions(-)
diff --git a/include/operating_system.h b/include/operating_system.h
index a2f5e2c..ff8e3ef 100644
--- a/include/operating_system.h
+++ b/include/operating_system.h
@@ -52,6 +52,9 @@ bool
sr_operating_system_parse_etc_system_release(const char *etc_system_release,
char **name,
char **version);
+bool
+sr_operating_system_parse_etc_os_release(const char *etc_os_release,
+ struct sr_operating_system *operating_system);
#ifdef __cplusplus
}
diff --git a/include/utils.h b/include/utils.h
index 5684620..fe8b13f 100644
--- a/include/utils.h
+++ b/include/utils.h
@@ -381,6 +381,16 @@ sr_indent_except_first_line(const char *input, int spaces);
char *
sr_build_path(const char *first_element, ...);
+/**
+ * Parses /etc/os-release file, see
+ *
http://www.freedesktop.org/software/systemd/man/os-release.html for more
+ * information. Calls callback for each key-value pair it reads from the file.
+ */
+void
+sr_parse_os_release(const char *input,
+ void (*callback)(char*, char*, void*),
+ void *data);
+
#ifdef __cplusplus
}
#endif
diff --git a/lib/abrt.c b/lib/abrt.c
index 1b13c37..cdee0c6 100644
--- a/lib/abrt.c
+++ b/lib/abrt.c
@@ -278,17 +278,30 @@ struct sr_operating_system *
sr_abrt_operating_system_from_dir(const char *directory,
char **error_message)
{
- char *release_contents = file_contents(directory, "os_release",
- error_message);
- if (!release_contents)
- return NULL;
-
+ bool success = false;
struct sr_operating_system *os = sr_operating_system_new();
- bool success = sr_operating_system_parse_etc_system_release(release_contents,
- &os->name,
- &os->version);
- free(release_contents);
+ char *osinfo_contents = file_contents(directory, "os_info",
error_message);
+ if (osinfo_contents)
+ {
+ success = sr_operating_system_parse_etc_os_release(osinfo_contents, os);
+ free(osinfo_contents);
+ }
+
+ /* fall back to os_release if parsing os_info fails */
+ if (!success)
+ {
+ char *release_contents = file_contents(directory, "os_release",
+ error_message);
+ if (release_contents)
+ {
+ success = sr_operating_system_parse_etc_system_release(release_contents,
+ &os->name,
+ &os->version);
+ free(release_contents);
+ }
+ }
+
if (!success)
{
sr_operating_system_free(os);
diff --git a/lib/operating_system.c b/lib/operating_system.c
index cfb33bc..a93175c 100644
--- a/lib/operating_system.c
+++ b/lib/operating_system.c
@@ -22,6 +22,7 @@
#include "utils.h"
#include "json.h"
#include "strbuf.h"
+#include "internal_utils.h"
#include <string.h>
#include <ctype.h>
#include <stddef.h>
@@ -133,3 +134,33 @@ sr_operating_system_parse_etc_system_release(const char
*etc_system_release,
*version = sr_strndup(version_begin, version_end - version_begin);
return true;
}
+
+static void
+os_release_callback(char *key, char *value, void *data)
+{
+ struct sr_operating_system *operating_system =
+ (struct sr_operating_system *)data;
+
+ if (0 == strcmp(key, "ID"))
+ {
+ operating_system->name = value;
+ }
+ else if (0 == strcmp(key, "VERSION_ID"))
+ {
+ operating_system->version = value;
+ }
+ else
+ {
+ free(value);
+ }
+ free(key);
+}
+
+bool
+sr_operating_system_parse_etc_os_release(const char *etc_os_release,
+ struct sr_operating_system *operating_system)
+{
+ sr_parse_os_release(etc_os_release, os_release_callback, (void*)operating_system);
+
+ return (operating_system->name && operating_system->version);
+}
diff --git a/lib/utils.c b/lib/utils.c
index 206c1fa..888d24d 100644
--- a/lib/utils.c
+++ b/lib/utils.c
@@ -706,3 +706,82 @@ sr_build_path(const char *first_element, ...)
va_end(elements);
return sr_strbuf_free_nobuf(strbuf);
}
+
+static void
+unescape_osinfo_value(const char *source, char* dest)
+{
+ while (source[0] != '\0')
+ {
+ if (source[0] == '\\')
+ { /* some characters may be escaped -> remove '\' */
+ ++source;
+ if (source[0] == '\0')
+ break;
+ *dest++ = *source++;
+ }
+ else if (source[0] == '\'' || source[0] == '"')
+ { /* skip non escaped quotes and don't care where they are */
+ ++source;
+ }
+ else
+ {
+ *dest++ = *source++;
+ }
+ }
+ dest[0] = '\0';
+}
+
+void
+sr_parse_os_release(const char *input, void (*callback)(char*, char*, void*),
+ void *data)
+{
+ const char *cursor = input;
+ unsigned line = 0;
+ while (cursor[0] != '\0')
+ {
+ ++line;
+ if (cursor[0] == '#')
+ goto skip_line;
+
+ const char *key_end = strchrnul(cursor, '=');
+ if (key_end[0] == '\0')
+ {
+ warn("os-release:%u: non empty last line", line);
+ break;
+ }
+
+ if (key_end - cursor == 0)
+ {
+ warn("os-release:%u: 0 length key", line);
+ goto skip_line;
+ }
+
+ const char *value_end = strchrnul(cursor, '\n');
+ if (key_end > value_end)
+ {
+ warn("os-release:%u: missing '='", line);
+ goto skip_line;
+ }
+
+ char *key = sr_strndup(cursor, key_end - cursor);
+ char *value = sr_strndup(key_end + 1, value_end - key_end - 1);
+ unescape_osinfo_value(value, value);
+
+ warn("os-release:%u: parsed line: '%s'='%s'", line,
key, value);
+
+ callback(key, value, data);
+
+ cursor = value_end;
+ if (value_end[0] == '\0')
+ {
+ warn("os-release:%u: the last value is not terminated by newline",
line);
+ }
+ else
+ ++cursor;
+
+ continue;
+ skip_line:
+ cursor = strchrnul(cursor, '\n');
+ cursor += (cursor[0] != '\0');
+ }
+}
diff --git a/tests/operating_system.at b/tests/operating_system.at
index 063f4c9..0c175c3 100644
--- a/tests/operating_system.at
+++ b/tests/operating_system.at
@@ -38,3 +38,60 @@ main(void)
return 0;
}
]])
+
+## ---------------------------------------- ##
+## sr_operating_system_parse_etc_os_release ##
+##----------------------------------------- ##
+AT_TESTFUN([sr_operating_system_parse_etc_os_release],
+[[
+#include "operating_system.h"
+#include "utils.h"
+#include <stdio.h>
+#include <assert.h>
+
+void check(const char *etc_os_release,
+ const char *expected_name,
+ const char *expected_version)
+{
+ struct sr_operating_system *os = sr_operating_system_new();
+ bool success = sr_operating_system_parse_etc_os_release(
+ etc_os_release, os);
+
+ assert(success);
+ assert(0 == strcmp(os->name, expected_name));
+ assert(0 == strcmp(os->version, expected_version));
+ sr_operating_system_free(os);
+}
+
+int
+main(void)
+{
+ char *f19 =
+"NAME=Fedora\n"
+"VERSION=\"19 (Schrödinger’s Cat)\"\n"
+"ID=fedora\n"
+"VERSION_ID=19\n"
+"PRETTY_NAME=\"Fedora 19 (Schrödinger’s Cat)\"\n"
+"ANSI_COLOR=\"0;34\"\n"
+"CPE_NAME=\"cpe:/o:fedoraproject:fedora:19\"\n";
+
+ char *el7 =
+"NAME=\"Red Hat Enterprise Linux Workstation\"\n"
+"VERSION=\"7.0 (Codename)\"\n"
+"ID=\"rhel\"\n"
+"VERSION_ID=\"7.0\"\n"
+"PRETTY_NAME=\"Red Hat Enterprise Linux Workstation 7.0
(Codename)\"\n"
+"ANSI_COLOR=\"0;31\"\n"
+"CPE_NAME=\"cpe:/o:redhat:enterprise_linux:7.0:beta:workstation\"\n"
+"\n"
+"REDHAT_BUGZILLA_PRODUCT=\"Red Hat Enterprise Linux 7\"\n"
+"REDHAT_BUGZILLA_PRODUCT_VERSION=7.0\n"
+"REDHAT_SUPPORT_PRODUCT=\"Red Hat Enterprise Linux\"\n"
+"REDHAT_SUPPORT_PRODUCT_VERSION=7.0\n";
+
+ check(f19, "fedora", "19");
+ check(el7, "rhel", "7.0");
+
+ return 0;
+}
+]])
--
1.8.3.1