summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/core/dbus-unit.c1
-rw-r--r--src/core/dbus-unit.h1
-rw-r--r--src/core/load-fragment-gperf.gperf.m41
-rw-r--r--src/core/load-fragment.c37
-rw-r--r--src/core/load-fragment.h1
-rw-r--r--src/core/unit.c8
-rw-r--r--src/core/unit.h1
-rw-r--r--src/shared/util.c21
-rw-r--r--src/shared/util.h2
-rw-r--r--src/systemctl/systemctl.c38
10 files changed, 108 insertions, 3 deletions
diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c
index 834fbd7693..812f1b9f16 100644
--- a/src/core/dbus-unit.c
+++ b/src/core/dbus-unit.c
@@ -808,6 +808,7 @@ const BusProperty bus_unit_properties[] = {
{ "PropagateReloadTo", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_PROPAGATE_RELOAD_TO]), true },
{ "PropagateReloadFrom", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_PROPAGATE_RELOAD_FROM]), true },
{ "RequiresMountsFor", bus_property_append_strv, "as", offsetof(Unit, requires_mounts_for), true },
+ { "Documentation", bus_property_append_strv, "as", offsetof(Unit, documentation), true },
{ "Description", bus_unit_append_description, "s", 0 },
{ "LoadState", bus_unit_append_load_state, "s", offsetof(Unit, load_state) },
{ "ActiveState", bus_unit_append_active_state, "s", 0 },
diff --git a/src/core/dbus-unit.h b/src/core/dbus-unit.h
index d22802d274..9680b56f06 100644
--- a/src/core/dbus-unit.h
+++ b/src/core/dbus-unit.h
@@ -87,6 +87,7 @@
" <property name=\"PropagateReloadFrom\" type=\"as\" access=\"read\"/>\n" \
" <property name=\"RequiresMountsFor\" type=\"as\" access=\"read\"/>\n" \
" <property name=\"Description\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"Documentation\" type=\"as\" access=\"read\"/>\n" \
" <property name=\"LoadState\" type=\"s\" access=\"read\"/>\n" \
" <property name=\"ActiveState\" type=\"s\" access=\"read\"/>\n" \
" <property name=\"SubState\" type=\"s\" access=\"read\"/>\n" \
diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
index d929273877..f0e25c0c5d 100644
--- a/src/core/load-fragment-gperf.gperf.m4
+++ b/src/core/load-fragment-gperf.gperf.m4
@@ -92,6 +92,7 @@ $1.ControlGroupPersistent, config_parse_tristate, 0,
)m4_dnl
Unit.Names, config_parse_unit_names, 0, 0
Unit.Description, config_parse_unit_string_printf, 0, offsetof(Unit, description)
+Unit.Documentation, config_parse_documentation, 0, offsetof(Unit, documentation)
Unit.Requires, config_parse_unit_deps, UNIT_REQUIRES, 0
Unit.RequiresOverridable, config_parse_unit_deps, UNIT_REQUIRES_OVERRIDABLE, 0
Unit.Requisite, config_parse_unit_deps, UNIT_REQUISITE, 0
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
index c2efec6657..3bc053341c 100644
--- a/src/core/load-fragment.c
+++ b/src/core/load-fragment.c
@@ -2063,6 +2063,43 @@ int config_parse_unit_requires_mounts_for(
return r;
}
+int config_parse_documentation(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Unit *u = userdata;
+ int r;
+ char **a, **b;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(u);
+
+ r = config_parse_unit_strv_printf(filename, line, section, lvalue, ltype, rvalue, data, userdata);
+ if (r < 0)
+ return r;
+
+ for (a = b = u->documentation; a && *a; a++) {
+
+ if (is_valid_documentation_url(*a))
+ *(b++) = *a;
+ else {
+ log_error("[%s:%u] Invalid URL, ignoring: %s", filename, line, *a);
+ free(*a);
+ }
+ }
+ *b = NULL;
+
+ return r;
+}
+
#define FOLLOW_MAX 8
static int open_follow(char **filename, FILE **_f, Set *names, char **_final) {
diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h
index ccc436420e..3b2ed096b5 100644
--- a/src/core/load-fragment.h
+++ b/src/core/load-fragment.h
@@ -36,6 +36,7 @@ int config_parse_unit_names(const char *filename, unsigned line, const char *sec
int config_parse_unit_string_printf(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_unit_strv_printf(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_unit_path_printf(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_documentation(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_socket_listen(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_socket_bind(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_exec_nice(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
diff --git a/src/core/unit.c b/src/core/unit.c
index 200d196878..1f1a5314f7 100644
--- a/src/core/unit.c
+++ b/src/core/unit.c
@@ -397,6 +397,7 @@ void unit_free(Unit *u) {
cgroup_attribute_free_list(u->cgroup_attributes);
free(u->description);
+ strv_free(u->documentation);
free(u->fragment_path);
free(u->instance);
@@ -624,7 +625,7 @@ const char *unit_description(Unit *u) {
}
void unit_dump(Unit *u, FILE *f, const char *prefix) {
- char *t;
+ char *t, **j;
UnitDependency d;
Iterator i;
char *p2;
@@ -672,6 +673,9 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) {
SET_FOREACH(t, u->names, i)
fprintf(f, "%s\tName: %s\n", prefix, t);
+ STRV_FOREACH(j, u->documentation)
+ fprintf(f, "%s\tDocumentation: %s\n", prefix, *j);
+
if ((following = unit_following(u)))
fprintf(f, "%s\tFollowing: %s\n", prefix, following->id);
@@ -698,8 +702,6 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) {
}
if (!strv_isempty(u->requires_mounts_for)) {
- char **j;
-
fprintf(f,
"%s\tRequiresMountsFor:", prefix);
diff --git a/src/core/unit.h b/src/core/unit.h
index e8e6b09866..87dc88c961 100644
--- a/src/core/unit.h
+++ b/src/core/unit.h
@@ -157,6 +157,7 @@ struct Unit {
char **requires_mounts_for;
char *description;
+ char **documentation;
char *fragment_path; /* if loaded from a config file this is the primary path to it */
usec_t fragment_mtime;
diff --git a/src/shared/util.c b/src/shared/util.c
index 0b81e1c4f9..ae0ce320ad 100644
--- a/src/shared/util.c
+++ b/src/shared/util.c
@@ -5610,3 +5610,24 @@ int can_sleep(const char *type) {
free(p);
return found;
}
+
+bool is_valid_documentation_url(const char *url) {
+ assert(url);
+
+ if (startswith(url, "http://") && url[7])
+ return true;
+
+ if (startswith(url, "https://") && url[8])
+ return true;
+
+ if (startswith(url, "file:") && url[5])
+ return true;
+
+ if (startswith(url, "info:") && url[5])
+ return true;
+
+ if (startswith(url, "man:") && url[4])
+ return true;
+
+ return false;
+}
diff --git a/src/shared/util.h b/src/shared/util.h
index f1bcb8a101..3dce047b40 100644
--- a/src/shared/util.h
+++ b/src/shared/util.h
@@ -508,4 +508,6 @@ int getenv_for_pid(pid_t pid, const char *field, char **_value);
int can_sleep(const char *type);
+bool is_valid_documentation_url(const char *url);
+
#endif
diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
index 92c79d038f..0d2044f874 100644
--- a/src/systemctl/systemctl.c
+++ b/src/systemctl/systemctl.c
@@ -2158,6 +2158,8 @@ typedef struct UnitStatusInfo {
const char *description;
const char *following;
+ char **documentation;
+
const char *path;
const char *default_control_group;
@@ -2303,6 +2305,19 @@ static void print_status_info(UnitStatusInfo *i) {
if (i->what)
printf("\t What: %s\n", i->what);
+ if (!strv_isempty(i->documentation)) {
+ char **t;
+ bool first = true;
+
+ STRV_FOREACH(t, i->documentation) {
+ if (first) {
+ printf("\t Docs: %s\n", *t);
+ first = false;
+ } else
+ printf("\t %s\n", *t);
+ }
+ }
+
if (i->accept)
printf("\tAccepted: %u; Connected: %u\n", i->n_accepted, i->n_connections);
@@ -2609,6 +2624,27 @@ static int status_property(const char *name, DBusMessageIter *iter, UnitStatusIn
dbus_message_iter_next(&sub);
}
+ } else if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING &&
+ streq(name, "Documentation")) {
+
+ DBusMessageIter sub;
+
+ dbus_message_iter_recurse(iter, &sub);
+ while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING) {
+ const char *s;
+ char **l;
+
+ dbus_message_iter_get_basic(&sub, &s);
+
+ l = strv_append(i->documentation, s);
+ if (!l)
+ return -ENOMEM;
+
+ strv_free(i->documentation);
+ i->documentation = l;
+
+ dbus_message_iter_next(&sub);
+ }
}
break;
@@ -2932,6 +2968,8 @@ static int show_one(const char *verb, DBusConnection *bus, const char *path, boo
if (!show_properties)
print_status_info(&info);
+ strv_free(info.documentation);
+
if (!streq_ptr(info.active_state, "active") &&
!streq_ptr(info.active_state, "reloading") &&
streq(verb, "status"))