mirror of
https://github.com/systemd/systemd
synced 2024-10-15 12:34:37 +00:00
notify: add minimal readiness/status protocol for spawned daemons
This commit is contained in:
parent
17586c16ba
commit
8c47c7325f
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
||||||
|
test-daemon
|
||||||
systemd-install
|
systemd-install
|
||||||
org.freedesktop.systemd1.*.xml
|
org.freedesktop.systemd1.*.xml
|
||||||
test-ns
|
test-ns
|
||||||
|
|
|
@ -67,7 +67,8 @@ noinst_PROGRAMS = \
|
||||||
test-engine \
|
test-engine \
|
||||||
test-job-type \
|
test-job-type \
|
||||||
test-ns \
|
test-ns \
|
||||||
test-loopback
|
test-loopback \
|
||||||
|
test-daemon
|
||||||
|
|
||||||
dist_dbuspolicy_DATA = \
|
dist_dbuspolicy_DATA = \
|
||||||
src/org.freedesktop.systemd1.conf
|
src/org.freedesktop.systemd1.conf
|
||||||
|
@ -316,8 +317,10 @@ test_loopback_SOURCES = \
|
||||||
src/test-loopback.c \
|
src/test-loopback.c \
|
||||||
src/loopback-setup.c
|
src/loopback-setup.c
|
||||||
|
|
||||||
test_loopback_CFLAGS = $(systemd_CFLAGS)
|
test_daemon_SOURCES = \
|
||||||
test_loopback_LDADD = $(systemd_LDADD)
|
$(BASIC_SOURCES) \
|
||||||
|
src/test-daemon.c \
|
||||||
|
src/sd-daemon.c
|
||||||
|
|
||||||
systemd_logger_SOURCES = \
|
systemd_logger_SOURCES = \
|
||||||
$(BASIC_SOURCES) \
|
$(BASIC_SOURCES) \
|
||||||
|
|
4
fixme
4
fixme
|
@ -1,4 +1,4 @@
|
||||||
* timer
|
* calendar time support in timer
|
||||||
|
|
||||||
* enforce max number of concurrent connection limit in sockets.
|
* enforce max number of concurrent connection limit in sockets.
|
||||||
|
|
||||||
|
@ -49,8 +49,6 @@
|
||||||
- bluetoothd (/var/run/sdp! @/org/bluez/audio!)
|
- bluetoothd (/var/run/sdp! @/org/bluez/audio!)
|
||||||
- distccd
|
- distccd
|
||||||
|
|
||||||
* regnerate unit/sysv search paths on daemon reload
|
|
||||||
|
|
||||||
* write utmp record a la upstart for processes
|
* write utmp record a la upstart for processes
|
||||||
|
|
||||||
* run PAM session stuff
|
* run PAM session stuff
|
||||||
|
|
31
src/cgroup.c
31
src/cgroup.c
|
@ -535,6 +535,37 @@ int cgroup_notify_empty(Manager *m, const char *group) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Unit* cgroup_unit_by_pid(Manager *m, pid_t pid) {
|
||||||
|
CGroupBonding *l, *b;
|
||||||
|
char *group = NULL;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(m);
|
||||||
|
|
||||||
|
if (pid <= 1)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if ((r = cgroup_get_current_controller_path(pid, m->cgroup_controller, &group)))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
l = hashmap_get(m->cgroup_bondings, group);
|
||||||
|
free(group);
|
||||||
|
|
||||||
|
if (!l)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
LIST_FOREACH(by_path, b, l) {
|
||||||
|
|
||||||
|
if (!b->unit)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (b->only_us)
|
||||||
|
return b->unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
CGroupBonding *cgroup_bonding_find_list(CGroupBonding *first, const char *controller) {
|
CGroupBonding *cgroup_bonding_find_list(CGroupBonding *first, const char *controller) {
|
||||||
CGroupBonding *b;
|
CGroupBonding *b;
|
||||||
|
|
||||||
|
|
|
@ -76,4 +76,6 @@ int manager_shutdown_cgroup(Manager *m, bool delete);
|
||||||
|
|
||||||
int cgroup_notify_empty(Manager *m, const char *group);
|
int cgroup_notify_empty(Manager *m, const char *group);
|
||||||
|
|
||||||
|
Unit* cgroup_unit_by_pid(Manager *m, pid_t pid);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -41,7 +41,8 @@
|
||||||
" <property name=\"ControlPID\" type=\"u\" access=\"read\"/>\n" \
|
" <property name=\"ControlPID\" type=\"u\" access=\"read\"/>\n" \
|
||||||
" <property name=\"SysVPath\" type=\"s\" access=\"read\"/>\n" \
|
" <property name=\"SysVPath\" type=\"s\" access=\"read\"/>\n" \
|
||||||
" <property name=\"BusName\" type=\"s\" access=\"read\"/>\n" \
|
" <property name=\"BusName\" type=\"s\" access=\"read\"/>\n" \
|
||||||
" </interface>\n"
|
" <property name=\"StatusText\" type=\"s\" access=\"read\"/>\n" \
|
||||||
|
" </interface>\n"
|
||||||
|
|
||||||
#define INTROSPECTION \
|
#define INTROSPECTION \
|
||||||
DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
|
DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
|
||||||
|
@ -76,6 +77,7 @@ DBusHandlerResult bus_service_message_handler(Unit *u, DBusMessage *message) {
|
||||||
{ "org.freedesktop.systemd1.Service", "ControlPID", bus_property_append_pid, "u", &u->service.control_pid },
|
{ "org.freedesktop.systemd1.Service", "ControlPID", bus_property_append_pid, "u", &u->service.control_pid },
|
||||||
{ "org.freedesktop.systemd1.Service", "SysVPath", bus_property_append_string, "s", u->service.sysv_path },
|
{ "org.freedesktop.systemd1.Service", "SysVPath", bus_property_append_string, "s", u->service.sysv_path },
|
||||||
{ "org.freedesktop.systemd1.Service", "BusName", bus_property_append_string, "s", u->service.bus_name },
|
{ "org.freedesktop.systemd1.Service", "BusName", bus_property_append_string, "s", u->service.bus_name },
|
||||||
|
{ "org.freedesktop.systemd1.Service", "StatusText", bus_property_append_string, "s", u->service.status_text },
|
||||||
{ NULL, NULL, NULL, NULL, NULL }
|
{ NULL, NULL, NULL, NULL, NULL }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -354,6 +354,10 @@ int main(int argc, char *argv[]) {
|
||||||
if (server_init(&server, (unsigned) n) < 0)
|
if (server_init(&server, (unsigned) n) < 0)
|
||||||
return 2;
|
return 2;
|
||||||
|
|
||||||
|
sd_notify(false,
|
||||||
|
"READY=1\n"
|
||||||
|
"STATUS=Processing requests...");
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
struct epoll_event event;
|
struct epoll_event event;
|
||||||
int k;
|
int k;
|
||||||
|
@ -378,6 +382,9 @@ int main(int argc, char *argv[]) {
|
||||||
r = 0;
|
r = 0;
|
||||||
|
|
||||||
fail:
|
fail:
|
||||||
|
sd_notify(false,
|
||||||
|
"STATUS=Shutting down...");
|
||||||
|
|
||||||
server_done(&server);
|
server_done(&server);
|
||||||
|
|
||||||
log_info("systemd-initctl stopped as pid %llu", (unsigned long long) getpid());
|
log_info("systemd-initctl stopped as pid %llu", (unsigned long long) getpid());
|
||||||
|
|
|
@ -547,6 +547,10 @@ int main(int argc, char *argv[]) {
|
||||||
if (server_init(&server, (unsigned) n) < 0)
|
if (server_init(&server, (unsigned) n) < 0)
|
||||||
return 3;
|
return 3;
|
||||||
|
|
||||||
|
sd_notify(false,
|
||||||
|
"READY=1\n"
|
||||||
|
"STATUS=Processing requests...");
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
struct epoll_event event;
|
struct epoll_event event;
|
||||||
int k;
|
int k;
|
||||||
|
@ -571,6 +575,9 @@ int main(int argc, char *argv[]) {
|
||||||
r = 0;
|
r = 0;
|
||||||
|
|
||||||
fail:
|
fail:
|
||||||
|
sd_notify(false,
|
||||||
|
"STATUS=Shutting down...");
|
||||||
|
|
||||||
server_done(&server);
|
server_done(&server);
|
||||||
|
|
||||||
log_info("systemd-logger stopped as pid %llu", (unsigned long long) getpid());
|
log_info("systemd-logger stopped as pid %llu", (unsigned long long) getpid());
|
||||||
|
|
161
src/manager.c
161
src/manager.c
|
@ -60,6 +60,67 @@
|
||||||
/* As soon as 5s passed since a unit was added to our GC queue, make sure to run a gc sweep */
|
/* As soon as 5s passed since a unit was added to our GC queue, make sure to run a gc sweep */
|
||||||
#define GC_QUEUE_USEC_MAX (10*USEC_PER_SEC)
|
#define GC_QUEUE_USEC_MAX (10*USEC_PER_SEC)
|
||||||
|
|
||||||
|
/* Where clients shall send notification messages to */
|
||||||
|
#define NOTIFY_SOCKET "/org/freedesktop/systemd1/notify"
|
||||||
|
|
||||||
|
static int manager_setup_notify(Manager *m) {
|
||||||
|
union {
|
||||||
|
struct sockaddr sa;
|
||||||
|
struct sockaddr_un un;
|
||||||
|
} sa;
|
||||||
|
struct epoll_event ev;
|
||||||
|
char *ne[2], **t;
|
||||||
|
int one = 1;
|
||||||
|
|
||||||
|
assert(m);
|
||||||
|
|
||||||
|
m->notify_watch.type = WATCH_NOTIFY;
|
||||||
|
if ((m->notify_watch.fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)) < 0) {
|
||||||
|
log_error("Failed to allocate notification socket: %m");
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
zero(sa);
|
||||||
|
sa.sa.sa_family = AF_UNIX;
|
||||||
|
|
||||||
|
if (m->running_as == MANAGER_SESSION)
|
||||||
|
snprintf(sa.un.sun_path+1, sizeof(sa.un.sun_path)-1, NOTIFY_SOCKET "/%llu", random_ull());
|
||||||
|
else
|
||||||
|
strncpy(sa.un.sun_path+1, NOTIFY_SOCKET, sizeof(sa.un.sun_path)-1);
|
||||||
|
|
||||||
|
if (bind(m->notify_watch.fd, &sa.sa, sizeof(sa)) < 0) {
|
||||||
|
log_error("bind() failed: %m");
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setsockopt(m->notify_watch.fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) {
|
||||||
|
log_error("SO_PASSCRED failed: %m");
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
zero(ev);
|
||||||
|
ev.events = EPOLLIN;
|
||||||
|
ev.data.ptr = &m->notify_watch;
|
||||||
|
|
||||||
|
if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->notify_watch.fd, &ev) < 0)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
if (asprintf(&ne[0], "NOTIFY_SOCKET=@%s", sa.un.sun_path+1) < 0)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
ne[1] = NULL;
|
||||||
|
t = strv_env_merge(m->environment, ne, NULL);
|
||||||
|
free(ne[0]);
|
||||||
|
|
||||||
|
if (!t)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
strv_free(m->environment);
|
||||||
|
m->environment = t;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int enable_special_signals(Manager *m) {
|
static int enable_special_signals(Manager *m) {
|
||||||
char fd;
|
char fd;
|
||||||
|
|
||||||
|
@ -177,6 +238,9 @@ int manager_new(ManagerRunningAs running_as, bool confirm_spawn, Manager **_m) {
|
||||||
if ((r = manager_setup_cgroup(m)) < 0)
|
if ((r = manager_setup_cgroup(m)) < 0)
|
||||||
goto fail;
|
goto fail;
|
||||||
|
|
||||||
|
if ((r = manager_setup_notify(m)) < 0)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
/* Try to connect to the busses, if possible. */
|
/* Try to connect to the busses, if possible. */
|
||||||
if ((r = bus_init_system(m)) < 0 ||
|
if ((r = bus_init_system(m)) < 0 ||
|
||||||
(r = bus_init_api(m)) < 0)
|
(r = bus_init_api(m)) < 0)
|
||||||
|
@ -364,6 +428,8 @@ void manager_free(Manager *m) {
|
||||||
close_nointr_nofail(m->epoll_fd);
|
close_nointr_nofail(m->epoll_fd);
|
||||||
if (m->signal_watch.fd >= 0)
|
if (m->signal_watch.fd >= 0)
|
||||||
close_nointr_nofail(m->signal_watch.fd);
|
close_nointr_nofail(m->signal_watch.fd);
|
||||||
|
if (m->notify_watch.fd >= 0)
|
||||||
|
close_nointr_nofail(m->notify_watch.fd);
|
||||||
|
|
||||||
lookup_paths_free(&m->lookup_paths);
|
lookup_paths_free(&m->lookup_paths);
|
||||||
strv_free(m->environment);
|
strv_free(m->environment);
|
||||||
|
@ -1521,12 +1587,82 @@ unsigned manager_dispatch_dbus_queue(Manager *m) {
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int manager_process_notify_fd(Manager *m) {
|
||||||
|
ssize_t n;
|
||||||
|
|
||||||
|
assert(m);
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
char buf[4096];
|
||||||
|
struct msghdr msghdr;
|
||||||
|
struct iovec iovec;
|
||||||
|
struct ucred *ucred;
|
||||||
|
union {
|
||||||
|
struct cmsghdr cmsghdr;
|
||||||
|
uint8_t buf[CMSG_SPACE(sizeof(struct ucred))];
|
||||||
|
} control;
|
||||||
|
Unit *u;
|
||||||
|
char **tags;
|
||||||
|
|
||||||
|
zero(iovec);
|
||||||
|
iovec.iov_base = buf;
|
||||||
|
iovec.iov_len = sizeof(buf)-1;
|
||||||
|
|
||||||
|
zero(control);
|
||||||
|
zero(msghdr);
|
||||||
|
msghdr.msg_iov = &iovec;
|
||||||
|
msghdr.msg_iovlen = 1;
|
||||||
|
msghdr.msg_control = &control;
|
||||||
|
msghdr.msg_controllen = sizeof(control);
|
||||||
|
|
||||||
|
if ((n = recvmsg(m->notify_watch.fd, &msghdr, MSG_DONTWAIT)) <= 0) {
|
||||||
|
if (n >= 0)
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
if (errno == EAGAIN)
|
||||||
|
break;
|
||||||
|
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msghdr.msg_controllen < CMSG_LEN(sizeof(struct ucred)) ||
|
||||||
|
control.cmsghdr.cmsg_level != SOL_SOCKET ||
|
||||||
|
control.cmsghdr.cmsg_type != SCM_CREDENTIALS ||
|
||||||
|
control.cmsghdr.cmsg_len != CMSG_LEN(sizeof(struct ucred))) {
|
||||||
|
log_warning("Received notify message without credentials. Ignoring.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr);
|
||||||
|
|
||||||
|
if (!(u = hashmap_get(m->watch_pids, UINT32_TO_PTR(ucred->pid))))
|
||||||
|
if (!(u = cgroup_unit_by_pid(m, ucred->pid))) {
|
||||||
|
log_warning("Cannot find unit for notify message of PID %lu.", (unsigned long) ucred->pid);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
char_array_0(buf);
|
||||||
|
if (!(tags = strv_split(buf, "\n\r")))
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
log_debug("Got notification message for unit %s", u->meta.id);
|
||||||
|
|
||||||
|
if (UNIT_VTABLE(u)->notify_message)
|
||||||
|
UNIT_VTABLE(u)->notify_message(u, tags);
|
||||||
|
|
||||||
|
strv_free(tags);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int manager_dispatch_sigchld(Manager *m) {
|
static int manager_dispatch_sigchld(Manager *m) {
|
||||||
assert(m);
|
assert(m);
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
siginfo_t si;
|
siginfo_t si;
|
||||||
Unit *u;
|
Unit *u;
|
||||||
|
int r;
|
||||||
|
|
||||||
zero(si);
|
zero(si);
|
||||||
|
|
||||||
|
@ -1555,6 +1691,17 @@ static int manager_dispatch_sigchld(Manager *m) {
|
||||||
free(name);
|
free(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Let's flush any message the dying child might still
|
||||||
|
* have queued for us. This ensures that the process
|
||||||
|
* still exists in /proc so that we can figure out
|
||||||
|
* which cgroup and hence unit it belongs to. */
|
||||||
|
if ((r = manager_process_notify_fd(m)) < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
/* And now figure out the unit this belongs to */
|
||||||
|
if (!(u = hashmap_get(m->watch_pids, UINT32_TO_PTR(si.si_pid))))
|
||||||
|
u = cgroup_unit_by_pid(m, si.si_pid);
|
||||||
|
|
||||||
/* And now, we actually reap the zombie. */
|
/* And now, we actually reap the zombie. */
|
||||||
if (waitid(P_PID, si.si_pid, &si, WEXITED) < 0) {
|
if (waitid(P_PID, si.si_pid, &si, WEXITED) < 0) {
|
||||||
if (errno == EINTR)
|
if (errno == EINTR)
|
||||||
|
@ -1572,11 +1719,12 @@ static int manager_dispatch_sigchld(Manager *m) {
|
||||||
si.si_status,
|
si.si_status,
|
||||||
strna(si.si_code == CLD_EXITED ? exit_status_to_string(si.si_status) : strsignal(si.si_status)));
|
strna(si.si_code == CLD_EXITED ? exit_status_to_string(si.si_status) : strsignal(si.si_status)));
|
||||||
|
|
||||||
if (!(u = hashmap_remove(m->watch_pids, UINT32_TO_PTR(si.si_pid))))
|
if (!u)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
log_debug("Child %llu belongs to %s", (long long unsigned) si.si_pid, u->meta.id);
|
log_debug("Child %llu belongs to %s", (long long unsigned) si.si_pid, u->meta.id);
|
||||||
|
|
||||||
|
hashmap_remove(m->watch_pids, UINT32_TO_PTR(si.si_pid));
|
||||||
UNIT_VTABLE(u)->sigchld_event(u, si.si_pid, si.si_code, si.si_status);
|
UNIT_VTABLE(u)->sigchld_event(u, si.si_pid, si.si_code, si.si_status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1738,6 +1886,17 @@ static int process_event(Manager *m, struct epoll_event *ev) {
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case WATCH_NOTIFY:
|
||||||
|
|
||||||
|
/* An incoming daemon notification event? */
|
||||||
|
if (ev->events != EPOLLIN)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if ((r = manager_process_notify_fd(m)) < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
case WATCH_FD:
|
case WATCH_FD:
|
||||||
|
|
||||||
/* Some fd event, to be dispatched to the units */
|
/* Some fd event, to be dispatched to the units */
|
||||||
|
|
|
@ -56,6 +56,7 @@ typedef enum ManagerRunningAs {
|
||||||
enum WatchType {
|
enum WatchType {
|
||||||
WATCH_INVALID,
|
WATCH_INVALID,
|
||||||
WATCH_SIGNAL,
|
WATCH_SIGNAL,
|
||||||
|
WATCH_NOTIFY,
|
||||||
WATCH_FD,
|
WATCH_FD,
|
||||||
WATCH_TIMER,
|
WATCH_TIMER,
|
||||||
WATCH_MOUNT,
|
WATCH_MOUNT,
|
||||||
|
@ -171,6 +172,7 @@ struct Manager {
|
||||||
|
|
||||||
Hashmap *watch_pids; /* pid => Unit object n:1 */
|
Hashmap *watch_pids; /* pid => Unit object n:1 */
|
||||||
|
|
||||||
|
Watch notify_watch;
|
||||||
Watch signal_watch;
|
Watch signal_watch;
|
||||||
|
|
||||||
int epoll_fd;
|
int epoll_fd;
|
||||||
|
@ -215,7 +217,6 @@ struct Manager {
|
||||||
char *cgroup_hierarchy;
|
char *cgroup_hierarchy;
|
||||||
|
|
||||||
usec_t gc_queue_timestamp;
|
usec_t gc_queue_timestamp;
|
||||||
|
|
||||||
int gc_marker;
|
int gc_marker;
|
||||||
unsigned n_in_gc_queue;
|
unsigned n_in_gc_queue;
|
||||||
|
|
||||||
|
|
|
@ -921,12 +921,14 @@ static void mount_sigchld_event(Unit *u, pid_t pid, int code, int status) {
|
||||||
assert(m);
|
assert(m);
|
||||||
assert(pid >= 0);
|
assert(pid >= 0);
|
||||||
|
|
||||||
|
if (pid != m->control_pid)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m->control_pid = 0;
|
||||||
|
|
||||||
success = is_clean_exit(code, status);
|
success = is_clean_exit(code, status);
|
||||||
m->failure = m->failure || !success;
|
m->failure = m->failure || !success;
|
||||||
|
|
||||||
assert(m->control_pid == pid);
|
|
||||||
m->control_pid = 0;
|
|
||||||
|
|
||||||
if (m->control_command) {
|
if (m->control_command) {
|
||||||
exec_status_fill(&m->control_command->exec_status, pid, code, status);
|
exec_status_fill(&m->control_command->exec_status, pid, code, status);
|
||||||
m->control_command = NULL;
|
m->control_command = NULL;
|
||||||
|
|
113
src/sd-daemon.c
113
src/sd-daemon.c
|
@ -24,6 +24,10 @@
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
***/
|
***/
|
||||||
|
|
||||||
|
#ifndef _GNU_SOURCE
|
||||||
|
#define _GNU_SOURCE
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
|
@ -34,12 +38,14 @@
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
#include "sd-daemon.h"
|
#include "sd-daemon.h"
|
||||||
|
|
||||||
int sd_listen_fds(int unset_environment) {
|
int sd_listen_fds(int unset_environment) {
|
||||||
|
|
||||||
#ifdef DISABLE_SYSTEMD
|
#if defined(DISABLE_SYSTEMD) || !defined(__linux__)
|
||||||
return 0;
|
return 0;
|
||||||
#else
|
#else
|
||||||
int r, fd;
|
int r, fd;
|
||||||
|
@ -317,3 +323,108 @@ int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int sd_notify(int unset_environment, const char *state) {
|
||||||
|
#if defined(DISABLE_SYSTEMD) || !defined(__linux__)
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
|
int fd = -1, r;
|
||||||
|
struct msghdr msghdr;
|
||||||
|
struct iovec iovec;
|
||||||
|
union sockaddr_union sockaddr;
|
||||||
|
struct ucred *ucred;
|
||||||
|
union {
|
||||||
|
struct cmsghdr cmsghdr;
|
||||||
|
uint8_t buf[CMSG_SPACE(sizeof(struct ucred))];
|
||||||
|
} control;
|
||||||
|
const char *e;
|
||||||
|
|
||||||
|
if (!state) {
|
||||||
|
r = -EINVAL;
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(e = getenv("NOTIFY_SOCKET"))) {
|
||||||
|
r = 0;
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Must be an abstract socket, or an absolute path */
|
||||||
|
if ((e[0] != '@' && e[0] != '/') || e[1] == 0) {
|
||||||
|
r = -EINVAL;
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0)) < 0) {
|
||||||
|
r = -errno;
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(&sockaddr, 0, sizeof(sockaddr));
|
||||||
|
sockaddr.sa.sa_family = AF_UNIX;
|
||||||
|
strncpy(sockaddr.un.sun_path, e, sizeof(sockaddr.un.sun_path));
|
||||||
|
|
||||||
|
if (sockaddr.un.sun_path[0] == '@')
|
||||||
|
sockaddr.un.sun_path[0] = 0;
|
||||||
|
|
||||||
|
memset(&iovec, 0, sizeof(iovec));
|
||||||
|
iovec.iov_base = (char*) state;
|
||||||
|
iovec.iov_len = strlen(state);
|
||||||
|
|
||||||
|
memset(&control, 0, sizeof(control));
|
||||||
|
control.cmsghdr.cmsg_level = SOL_SOCKET;
|
||||||
|
control.cmsghdr.cmsg_type = SCM_CREDENTIALS;
|
||||||
|
control.cmsghdr.cmsg_len = CMSG_LEN(sizeof(struct ucred));
|
||||||
|
|
||||||
|
ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr);
|
||||||
|
ucred->pid = getpid();
|
||||||
|
ucred->uid = getuid();
|
||||||
|
ucred->gid = getgid();
|
||||||
|
|
||||||
|
memset(&msghdr, 0, sizeof(msghdr));
|
||||||
|
msghdr.msg_name = &sockaddr;
|
||||||
|
msghdr.msg_namelen = sizeof(struct sockaddr_un);
|
||||||
|
msghdr.msg_iov = &iovec;
|
||||||
|
msghdr.msg_iovlen = 1;
|
||||||
|
msghdr.msg_control = &control;
|
||||||
|
msghdr.msg_controllen = control.cmsghdr.cmsg_len;
|
||||||
|
|
||||||
|
if (sendmsg(fd, &msghdr, MSG_NOSIGNAL) < 0) {
|
||||||
|
r = -errno;
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = 0;
|
||||||
|
|
||||||
|
finish:
|
||||||
|
if (unset_environment)
|
||||||
|
unsetenv("NOTIFY_SOCKET");
|
||||||
|
|
||||||
|
if (fd >= 0)
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
return r;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int sd_notifyf(int unset_environment, const char *format, ...) {
|
||||||
|
#if defined(DISABLE_SYSTEMD) || !defined(__linux__)
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
|
va_list ap;
|
||||||
|
char *p = NULL;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
va_start(ap, format);
|
||||||
|
r = vasprintf(&p, format, ap);
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
if (r < 0 || !p)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
r = sd_notify(unset_environment, p);
|
||||||
|
free(p);
|
||||||
|
|
||||||
|
return r;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
|
@ -27,8 +27,13 @@
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
***/
|
***/
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Reference implementation of a few systemd related interfaces for
|
/* Reference implementation of a few systemd related interfaces for
|
||||||
* writing daemons. These interfaces are trivial to implement. To
|
* writing daemons. These interfaces are trivial to implement. To
|
||||||
* simplify porting we provide this reference
|
* simplify porting we provide this reference
|
||||||
|
@ -111,4 +116,58 @@ int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port
|
||||||
* errno style error code on failure. */
|
* errno style error code on failure. */
|
||||||
int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length);
|
int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length);
|
||||||
|
|
||||||
|
/* Informs systemd about changed daemon state. This takes a numeber of
|
||||||
|
* newline seperated environment-style variable assignments in a
|
||||||
|
* string. The following strings are known:
|
||||||
|
*
|
||||||
|
* READY=1 Tells systemd that daemon startup is finished (only
|
||||||
|
* relevant for services of Type=notify). The passed
|
||||||
|
* argument is a boolean "1" or "0". Since there is
|
||||||
|
* little value in signalling non-readiness the only
|
||||||
|
* value daemons should send is "READY=1".
|
||||||
|
*
|
||||||
|
* STATUS=... Passes a status string back to systemd that
|
||||||
|
* describes the daemon state. This is free-from and
|
||||||
|
* can be used for various purposes: general state
|
||||||
|
* feedback, fsck-like programs could pass completion
|
||||||
|
* percentages and failing programs could pass a human
|
||||||
|
* readable error message. Example: "STATUS=Completed
|
||||||
|
* 66% of file system check..."
|
||||||
|
*
|
||||||
|
* ERRNO=... If a daemon fails, the errno-style error code,
|
||||||
|
* formatted as string. Example: "ERRNO=2" for ENOENT.
|
||||||
|
*
|
||||||
|
* BUSERROR=... If a daemon fails, the D-Bus error-style error
|
||||||
|
* code. Example: "BUSERROR=org.freedesktop.DBus.Error.TimedOut"
|
||||||
|
*
|
||||||
|
* MAINPID=... The main pid of a daemon, in case systemd did not
|
||||||
|
* fork off the process itself. Example: "MAINPID=4711"
|
||||||
|
*
|
||||||
|
* See sd_notifyf() for more complete examples.
|
||||||
|
*/
|
||||||
|
int sd_notify(int unset_environment, const char *state);
|
||||||
|
|
||||||
|
/* Similar to sd_send_state() but takes a format string.
|
||||||
|
*
|
||||||
|
* Example 1: A daemon could send the following after initialization:
|
||||||
|
*
|
||||||
|
* sd_notifyf(0, "READY=1\n"
|
||||||
|
* "STATUS=Processing requests...\n"
|
||||||
|
* "MAINPID=%lu",
|
||||||
|
* (unsigned long) getpid());
|
||||||
|
*
|
||||||
|
* Example 2: A daemon could send the following shortly before
|
||||||
|
* exiting, on failure:
|
||||||
|
*
|
||||||
|
* sd_notifyf(0, "STATUS=Failed to start up: %s\n"
|
||||||
|
* "ERRNO=%i",
|
||||||
|
* strerror(errno),
|
||||||
|
* errno);
|
||||||
|
*/
|
||||||
|
int sd_notifyf(int unset_environment, const char *format, ...);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -149,6 +149,9 @@ static void service_done(Unit *u) {
|
||||||
free(s->sysv_runlevels);
|
free(s->sysv_runlevels);
|
||||||
s->sysv_runlevels = NULL;
|
s->sysv_runlevels = NULL;
|
||||||
|
|
||||||
|
free(s->status_text);
|
||||||
|
s->status_text = NULL;
|
||||||
|
|
||||||
exec_context_done(&s->exec_context);
|
exec_context_done(&s->exec_context);
|
||||||
exec_command_free_array(s->exec_command, _SERVICE_EXEC_COMMAND_MAX);
|
exec_command_free_array(s->exec_command, _SERVICE_EXEC_COMMAND_MAX);
|
||||||
s->control_command = NULL;
|
s->control_command = NULL;
|
||||||
|
@ -907,6 +910,10 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) {
|
||||||
fprintf(f, "%sSysVRunLevels: %s\n",
|
fprintf(f, "%sSysVRunLevels: %s\n",
|
||||||
prefix, s->sysv_runlevels);
|
prefix, s->sysv_runlevels);
|
||||||
|
|
||||||
|
if (s->status_text)
|
||||||
|
fprintf(f, "%sStatus Text: %s\n",
|
||||||
|
prefix, s->status_text);
|
||||||
|
|
||||||
free(p2);
|
free(p2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1120,7 +1127,9 @@ static int service_coldplug(Unit *u) {
|
||||||
|
|
||||||
if ((s->deserialized_state == SERVICE_START &&
|
if ((s->deserialized_state == SERVICE_START &&
|
||||||
(s->type == SERVICE_FORKING ||
|
(s->type == SERVICE_FORKING ||
|
||||||
s->type == SERVICE_DBUS)) ||
|
s->type == SERVICE_DBUS ||
|
||||||
|
s->type == SERVICE_FINISH ||
|
||||||
|
s->type == SERVICE_NOTIFY)) ||
|
||||||
s->deserialized_state == SERVICE_START_POST ||
|
s->deserialized_state == SERVICE_START_POST ||
|
||||||
s->deserialized_state == SERVICE_RUNNING ||
|
s->deserialized_state == SERVICE_RUNNING ||
|
||||||
s->deserialized_state == SERVICE_RELOAD ||
|
s->deserialized_state == SERVICE_RELOAD ||
|
||||||
|
@ -1541,7 +1550,7 @@ static void service_enter_start(Service *s) {
|
||||||
|
|
||||||
if ((r = service_spawn(s,
|
if ((r = service_spawn(s,
|
||||||
s->exec_command[SERVICE_EXEC_START],
|
s->exec_command[SERVICE_EXEC_START],
|
||||||
s->type == SERVICE_FORKING || s->type == SERVICE_DBUS,
|
s->type == SERVICE_FORKING || s->type == SERVICE_DBUS || s->type == SERVICE_NOTIFY,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
|
@ -1569,13 +1578,15 @@ static void service_enter_start(Service *s) {
|
||||||
service_set_state(s, SERVICE_START);
|
service_set_state(s, SERVICE_START);
|
||||||
|
|
||||||
} else if (s->type == SERVICE_FINISH ||
|
} else if (s->type == SERVICE_FINISH ||
|
||||||
s->type == SERVICE_DBUS) {
|
s->type == SERVICE_DBUS ||
|
||||||
|
s->type == SERVICE_NOTIFY) {
|
||||||
|
|
||||||
/* For finishing services we wait until the start
|
/* For finishing services we wait until the start
|
||||||
* process exited, too, but it is our main process. */
|
* process exited, too, but it is our main process. */
|
||||||
|
|
||||||
/* For D-Bus services we know the main pid right away,
|
/* For D-Bus services we know the main pid right away,
|
||||||
* but wait for the bus name to appear on the bus. */
|
* but wait for the bus name to appear on the
|
||||||
|
* bus. Notify services are similar. */
|
||||||
|
|
||||||
s->main_pid = pid;
|
s->main_pid = pid;
|
||||||
s->main_pid_known = true;
|
s->main_pid_known = true;
|
||||||
|
@ -1946,7 +1957,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
|
||||||
exec_status_fill(&s->main_exec_status, pid, code, status);
|
exec_status_fill(&s->main_exec_status, pid, code, status);
|
||||||
s->main_pid = 0;
|
s->main_pid = 0;
|
||||||
|
|
||||||
if (s->type == SERVICE_SIMPLE || s->type == SERVICE_FINISH) {
|
if (s->type != SERVICE_FORKING) {
|
||||||
assert(s->exec_command[SERVICE_EXEC_START]);
|
assert(s->exec_command[SERVICE_EXEC_START]);
|
||||||
s->exec_command[SERVICE_EXEC_START]->exec_status = s->main_exec_status;
|
s->exec_command[SERVICE_EXEC_START]->exec_status = s->main_exec_status;
|
||||||
}
|
}
|
||||||
|
@ -1974,7 +1985,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
|
||||||
service_enter_signal(s, SERVICE_FINAL_SIGTERM, false);
|
service_enter_signal(s, SERVICE_FINAL_SIGTERM, false);
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
assert(s->type == SERVICE_DBUS);
|
assert(s->type == SERVICE_DBUS || s->type == SERVICE_NOTIFY);
|
||||||
|
|
||||||
/* Fall through */
|
/* Fall through */
|
||||||
}
|
}
|
||||||
|
@ -2101,8 +2112,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
|
||||||
assert_not_reached("Uh, control process died at wrong time.");
|
assert_not_reached("Uh, control process died at wrong time.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else
|
}
|
||||||
assert_not_reached("Got SIGCHLD for unkown PID");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void service_timer_event(Unit *u, uint64_t elapsed, Watch* w) {
|
static void service_timer_event(Unit *u, uint64_t elapsed, Watch* w) {
|
||||||
|
@ -2195,6 +2205,57 @@ static void service_cgroup_notify_event(Unit *u) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void service_notify_message(Unit *u, char **tags) {
|
||||||
|
Service *s = SERVICE(u);
|
||||||
|
const char *e;
|
||||||
|
|
||||||
|
assert(u);
|
||||||
|
|
||||||
|
log_debug("%s: Got message", u->meta.id);
|
||||||
|
|
||||||
|
/* Interpret MAINPID= */
|
||||||
|
if ((e = strv_find_prefix(tags, "MAINPID=")) &&
|
||||||
|
(s->state == SERVICE_START ||
|
||||||
|
s->state == SERVICE_START_POST ||
|
||||||
|
s->state == SERVICE_RUNNING ||
|
||||||
|
s->state == SERVICE_RELOAD)) {
|
||||||
|
unsigned long pid;
|
||||||
|
|
||||||
|
if (safe_atolu(e + 8, &pid) < 0 ||
|
||||||
|
(unsigned long) (pid_t) pid != pid ||
|
||||||
|
pid <= 1)
|
||||||
|
log_warning("Failed to parse %s", e);
|
||||||
|
else {
|
||||||
|
log_debug("%s: got %s", u->meta.id, e);
|
||||||
|
s->main_pid = (pid_t) pid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Interpret READY= */
|
||||||
|
if (s->type == SERVICE_NOTIFY &&
|
||||||
|
s->state == SERVICE_START &&
|
||||||
|
strv_find(tags, "READY=1")) {
|
||||||
|
log_debug("%s: got READY=1", u->meta.id);
|
||||||
|
|
||||||
|
service_enter_start_post(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Interpret STATUS= */
|
||||||
|
if ((e = strv_find_prefix(tags, "STATUS="))) {
|
||||||
|
char *t;
|
||||||
|
|
||||||
|
if (!(t = strdup(e+7))) {
|
||||||
|
log_error("Failed to allocate string.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_debug("%s: got %s", u->meta.id, e);
|
||||||
|
|
||||||
|
free(s->status_text);
|
||||||
|
s->status_text = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static int service_enumerate(Manager *m) {
|
static int service_enumerate(Manager *m) {
|
||||||
char **p;
|
char **p;
|
||||||
unsigned i;
|
unsigned i;
|
||||||
|
@ -2456,7 +2517,8 @@ static const char* const service_type_table[_SERVICE_TYPE_MAX] = {
|
||||||
[SERVICE_FORKING] = "forking",
|
[SERVICE_FORKING] = "forking",
|
||||||
[SERVICE_SIMPLE] = "simple",
|
[SERVICE_SIMPLE] = "simple",
|
||||||
[SERVICE_FINISH] = "finish",
|
[SERVICE_FINISH] = "finish",
|
||||||
[SERVICE_DBUS] = "dbus"
|
[SERVICE_DBUS] = "dbus",
|
||||||
|
[SERVICE_NOTIFY] = "notify"
|
||||||
};
|
};
|
||||||
|
|
||||||
DEFINE_STRING_TABLE_LOOKUP(service_type, ServiceType);
|
DEFINE_STRING_TABLE_LOOKUP(service_type, ServiceType);
|
||||||
|
@ -2502,6 +2564,7 @@ const UnitVTable service_vtable = {
|
||||||
.timer_event = service_timer_event,
|
.timer_event = service_timer_event,
|
||||||
|
|
||||||
.cgroup_notify_empty = service_cgroup_notify_event,
|
.cgroup_notify_empty = service_cgroup_notify_event,
|
||||||
|
.notify_message = service_notify_message,
|
||||||
|
|
||||||
.bus_name_owner_change = service_bus_name_owner_change,
|
.bus_name_owner_change = service_bus_name_owner_change,
|
||||||
.bus_query_pid_done = service_bus_query_pid_done,
|
.bus_query_pid_done = service_bus_query_pid_done,
|
||||||
|
|
|
@ -60,6 +60,7 @@ typedef enum ServiceType {
|
||||||
SERVICE_FORKING, /* forks by itself (i.e. traditional daemons) */
|
SERVICE_FORKING, /* forks by itself (i.e. traditional daemons) */
|
||||||
SERVICE_FINISH, /* we fork and wait until the program finishes (i.e. programs like fsck which run and need to finish before we continue) */
|
SERVICE_FINISH, /* we fork and wait until the program finishes (i.e. programs like fsck which run and need to finish before we continue) */
|
||||||
SERVICE_DBUS, /* we fork and wait until a specific D-Bus name appears on the bus */
|
SERVICE_DBUS, /* we fork and wait until a specific D-Bus name appears on the bus */
|
||||||
|
SERVICE_NOTIFY, /* we fork and wait until a daemon sends us a ready message with sd_notify() */
|
||||||
_SERVICE_TYPE_MAX,
|
_SERVICE_TYPE_MAX,
|
||||||
_SERVICE_TYPE_INVALID = -1
|
_SERVICE_TYPE_INVALID = -1
|
||||||
} ServiceType;
|
} ServiceType;
|
||||||
|
@ -121,6 +122,8 @@ struct Service {
|
||||||
|
|
||||||
char *bus_name;
|
char *bus_name;
|
||||||
|
|
||||||
|
char *status_text;
|
||||||
|
|
||||||
RateLimit ratelimit;
|
RateLimit ratelimit;
|
||||||
|
|
||||||
int socket_fd;
|
int socket_fd;
|
||||||
|
|
|
@ -1228,12 +1228,14 @@ static void socket_sigchld_event(Unit *u, pid_t pid, int code, int status) {
|
||||||
assert(s);
|
assert(s);
|
||||||
assert(pid >= 0);
|
assert(pid >= 0);
|
||||||
|
|
||||||
|
if (pid != s->control_pid)
|
||||||
|
return;
|
||||||
|
|
||||||
|
s->control_pid = 0;
|
||||||
|
|
||||||
success = is_clean_exit(code, status);
|
success = is_clean_exit(code, status);
|
||||||
s->failure = s->failure || !success;
|
s->failure = s->failure || !success;
|
||||||
|
|
||||||
assert(s->control_pid == pid);
|
|
||||||
s->control_pid = 0;
|
|
||||||
|
|
||||||
if (s->control_command)
|
if (s->control_command)
|
||||||
exec_status_fill(&s->control_command->exec_status, pid, code, status);
|
exec_status_fill(&s->control_command->exec_status, pid, code, status);
|
||||||
|
|
||||||
|
|
37
src/test-daemon.c
Normal file
37
src/test-daemon.c
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
/*-*- Mode: C; c-basic-offset: 8 -*-*/
|
||||||
|
|
||||||
|
/***
|
||||||
|
This file is part of systemd.
|
||||||
|
|
||||||
|
Copyright 2010 Lennart Poettering
|
||||||
|
|
||||||
|
systemd 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.
|
||||||
|
|
||||||
|
systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
***/
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "sd-daemon.h"
|
||||||
|
|
||||||
|
int main(int argc, char*argv[]) {
|
||||||
|
|
||||||
|
sd_notify(0, "STATUS=Starting up");
|
||||||
|
sleep(5);
|
||||||
|
sd_notify(0,
|
||||||
|
"STATUS=Running\n"
|
||||||
|
"READY=1");
|
||||||
|
sleep(10);
|
||||||
|
sd_notify(0, "STATUS=Quitting");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -285,6 +285,9 @@ struct UnitVTable {
|
||||||
* ran empty */
|
* ran empty */
|
||||||
void (*cgroup_notify_empty)(Unit *u);
|
void (*cgroup_notify_empty)(Unit *u);
|
||||||
|
|
||||||
|
/* Called whenever a process of this unit sends us a message */
|
||||||
|
void (*notify_message)(Unit *u, char **tags);
|
||||||
|
|
||||||
/* Called whenever a name thus Unit registered for comes or
|
/* Called whenever a name thus Unit registered for comes or
|
||||||
* goes away. */
|
* goes away. */
|
||||||
void (*bus_name_owner_change)(Unit *u, const char *name, const char *old_owner, const char *new_owner);
|
void (*bus_name_owner_change)(Unit *u, const char *name, const char *old_owner, const char *new_owner);
|
||||||
|
|
Loading…
Reference in a new issue