mirror of
https://github.com/systemd/systemd-stable.git
synced 2024-12-22 13:33:56 +03:00
Merge pull request #9121 from poettering/sd-event-inotify
add "sd_event_add_inotify()" and use it for making PID 1 rescheduler .timer units properly on timezone change
This commit is contained in:
commit
6585f72c08
9
TODO
9
TODO
@ -24,16 +24,13 @@ Janitorial Clean-ups:
|
||||
|
||||
Features:
|
||||
|
||||
* Add OnTimezoneChange= and OnTimeChange= stanzas to .timer units in order to
|
||||
schedule events based on time and timezone changes.
|
||||
|
||||
* add O_TMPFILE support to copy_file_atomic()
|
||||
|
||||
* nspawn: greater control over selinux label?
|
||||
|
||||
* sd-event: implement inotify events, as we can safely and robustly do that now
|
||||
for any inode without fearing confusion by inodes appearing at multiple
|
||||
places: we can open it with O_PATH first, then store its inode in a hash
|
||||
table, to recognize duplicate watches before creating (and thus corrupting
|
||||
pre-existing ones) them, and using /proc/self/fd/ to add it right after.
|
||||
|
||||
* the error paths in usbffs_dispatch_ep() leak memory
|
||||
|
||||
* cgroups: figure out if we can somehow communicate in a cleaner way whether a
|
||||
|
@ -263,6 +263,10 @@ manpages = [
|
||||
'3',
|
||||
['sd_event_add_exit', 'sd_event_add_post', 'sd_event_handler_t'],
|
||||
''],
|
||||
['sd_event_add_inotify',
|
||||
'3',
|
||||
['sd_event_inotify_handler_t', 'sd_event_source_get_inotify_mask'],
|
||||
''],
|
||||
['sd_event_add_io',
|
||||
'3',
|
||||
['sd_event_io_handler_t',
|
||||
|
@ -62,6 +62,7 @@
|
||||
<citerefentry><refentrytitle>sd_event_add_time</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_add_signal</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_add_child</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_add_inotify</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_add_defer</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_source_unref</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_source_set_priority</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
@ -152,6 +153,7 @@
|
||||
<citerefentry><refentrytitle>sd_event_add_time</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_add_signal</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_add_child</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_add_inotify</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_add_defer</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_source_unref</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_source_set_priority</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
|
@ -223,6 +223,7 @@
|
||||
<citerefentry><refentrytitle>sd_event_add_io</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_add_time</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_add_signal</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_add_inotify</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_add_defer</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_source_set_enabled</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_source_set_priority</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
|
@ -194,6 +194,7 @@
|
||||
<citerefentry><refentrytitle>sd_event_add_time</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_add_signal</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_add_child</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_add_inotify</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_source_set_enabled</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_source_set_priority</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_source_set_userdata</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
|
181
man/sd_event_add_inotify.xml
Normal file
181
man/sd_event_add_inotify.xml
Normal file
@ -0,0 +1,181 @@
|
||||
<?xml version='1.0'?> <!--*- Mode: nxml; nxml-child-indent: 2; indent-tabs-mode: nil -*-->
|
||||
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
|
||||
|
||||
<!-- SPDX-License-Identifier: LGPL-2.1+ -->
|
||||
|
||||
<refentry id="sd_event_add_inotify" xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
|
||||
<refentryinfo>
|
||||
<title>sd_event_add_inotify</title>
|
||||
<productname>systemd</productname>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>sd_event_add_inotify</refentrytitle>
|
||||
<manvolnum>3</manvolnum>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname>sd_event_add_inotify</refname>
|
||||
<refname>sd_event_source_get_inotify_mask</refname>
|
||||
<refname>sd_event_inotify_handler_t</refname>
|
||||
|
||||
<refpurpose>Add an "inotify" file system inode event source to an event loop</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<refsynopsisdiv>
|
||||
<funcsynopsis>
|
||||
<funcsynopsisinfo>#include <systemd/sd-event.h></funcsynopsisinfo>
|
||||
|
||||
<funcsynopsisinfo><token>typedef</token> struct sd_event_source sd_event_source;</funcsynopsisinfo>
|
||||
|
||||
<funcprototype>
|
||||
<funcdef>typedef int (*<function>sd_event_inotify_handler_t</function>)</funcdef>
|
||||
<paramdef>sd_event_source *<parameter>s</parameter></paramdef>
|
||||
<paramdef>const struct inotify_event *<parameter>event</parameter></paramdef>
|
||||
<paramdef>void *<parameter>userdata</parameter></paramdef>
|
||||
</funcprototype>
|
||||
|
||||
<funcprototype>
|
||||
<funcdef>int <function>sd_event_add_inotify</function></funcdef>
|
||||
<paramdef>sd_event *<parameter>event</parameter></paramdef>
|
||||
<paramdef>sd_event_source **<parameter>source</parameter></paramdef>
|
||||
<paramdef>const char *<parameter>path</parameter></paramdef>
|
||||
<paramdef>uint32_t <parameter>mask</parameter></paramdef>
|
||||
<paramdef>sd_event_inotify_handler_t <parameter>handler</parameter></paramdef>
|
||||
<paramdef>void *<parameter>userdata</parameter></paramdef>
|
||||
</funcprototype>
|
||||
|
||||
<funcprototype>
|
||||
<funcdef>int <function>sd_event_source_get_inotify_mask</function></funcdef>
|
||||
<paramdef>sd_event_source *<parameter>source</parameter></paramdef>
|
||||
<paramdef>uint32_t *<parameter>mask</parameter></paramdef>
|
||||
</funcprototype>
|
||||
|
||||
</funcsynopsis>
|
||||
</refsynopsisdiv>
|
||||
|
||||
<refsect1>
|
||||
<title>Description</title>
|
||||
|
||||
<para><function>sd_event_add_inotify()</function> adds a new <citerefentry
|
||||
project='man-pages'><refentrytitle>inotify</refentrytitle><manvolnum>7</manvolnum></citerefentry> file system inode
|
||||
event source to an event loop. The event loop object is specified in the <parameter>event</parameter> parameter,
|
||||
the event source object is returned in the <parameter>source</parameter> parameter. The <parameter>path</parameter>
|
||||
parameter specifies the path of the file system inode to watch. The <parameter>handler</parameter> must reference a
|
||||
function to call when the inode changes. The handler function will be passed the <parameter>userdata</parameter>
|
||||
pointer, which may be chosen freely by the caller. The handler also receives a pointer to a <structname>struct
|
||||
inotify_event</structname> structure containing information about the inode event. The <parameter>mask</parameter>
|
||||
parameter specifie which types of inode events to watch specifically. It must contain an OR-ed combination of
|
||||
<constant>IN_ACCESS</constant>, <constant>IN_ATTRIB</constant>, <constant>IN_CLOSE_WRITE</constant>, … flags. See
|
||||
<citerefentry project='man-pages'><refentrytitle>inotify</refentrytitle><manvolnum>7</manvolnum></citerefentry> for
|
||||
further information.</para>
|
||||
|
||||
<para>If multiple event sources are installed for the same inode the backing inotify watch descriptor is
|
||||
automatically shared. The mask parameter may contain any flag defined by the inotify API, with the exception of
|
||||
<constant>IN_MASK_ADD</constant>.</para>
|
||||
|
||||
<para>The handler is enabled continuously (<constant>SD_EVENT_ON</constant>), but this may be changed with
|
||||
<citerefentry><refentrytitle>sd_event_source_set_enabled</refentrytitle><manvolnum>3</manvolnum></citerefentry>. Alternatively,
|
||||
the <constant>IN_ONESHOT</constant> mask flag may be used to request <constant>SD_EVENT_ONESHOT</constant> mode.
|
||||
If the handler function returns a negative error code, it will be disabled after the invocation, even if the
|
||||
<constant>SD_EVENT_ON</constant> mode was requested before.
|
||||
</para>
|
||||
|
||||
<para>As a special limitation the priority of inotify event sources may only be altered (see
|
||||
<citerefentry><refentrytitle>sd_event_source_set_priority</refentrytitle><manvolnum>3</manvolnum></citerefentry>)
|
||||
in the time between creation of the event source object with <function>sd_event_add_inotify()</function> and the
|
||||
beginning of the next event loop iteration. Attempts of changing the priority any later will be refused. Consider
|
||||
freeing and allocating a new inotify event source to change the priority at that point.</para>
|
||||
|
||||
<para>To destroy an event source object use
|
||||
<citerefentry><refentrytitle>sd_event_source_unref</refentrytitle><manvolnum>3</manvolnum></citerefentry>, but note
|
||||
that the event source is only removed from the event loop when all references to the event source are dropped. To
|
||||
make sure an event source does not fire anymore, even when there's still a reference to it kept, consider disabling
|
||||
it with
|
||||
<citerefentry><refentrytitle>sd_event_source_set_enabled</refentrytitle><manvolnum>3</manvolnum></citerefentry>.</para>
|
||||
|
||||
<para>If the second parameter of <function>sd_event_add_inotify()</function> is passed as NULL no reference to the
|
||||
event source object is returned. In this case the event source is considered "floating", and will be destroyed
|
||||
implicitly when the event loop itself is destroyed.</para>
|
||||
|
||||
<para><function>sd_event_source_get_inotify_mask()</function> retrieves the configured inotify watch mask of an
|
||||
event source created previously with <function>sd_event_add_inotify()</function>. It takes the event source object
|
||||
as the <parameter>source</parameter> parameter and a pointer to a <type>uint32_t</type> variable to return the mask
|
||||
in.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Return Value</title>
|
||||
|
||||
<para>On success, these functions return 0 or a positive integer. On failure, they return a negative errno-style
|
||||
error code.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Errors</title>
|
||||
|
||||
<para>Returned errors may indicate the following problems:</para>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><constant>-ENOMEM</constant></term>
|
||||
|
||||
<listitem><para>Not enough memory to allocate an object.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><constant>-EINVAL</constant></term>
|
||||
|
||||
<listitem><para>An invalid argument has been passed. This includes specifying a mask with
|
||||
<constant>IN_MASK_ADD</constant> set.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><constant>-ESTALE</constant></term>
|
||||
|
||||
<listitem><para>The event loop is already terminated.</para></listitem>
|
||||
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><constant>-ECHILD</constant></term>
|
||||
|
||||
<listitem><para>The event loop has been created in a different process.</para></listitem>
|
||||
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><constant>-EDOM</constant></term>
|
||||
|
||||
<listitem><para>The passed event source is not an inotify process event source.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<xi:include href="libsystemd-pkgconfig.xml" />
|
||||
|
||||
<refsect1>
|
||||
<title>See Also</title>
|
||||
|
||||
<para>
|
||||
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd-event</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_new</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_now</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_add_io</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_add_time</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_add_signal</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_add_defer</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_add_child</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_source_set_enabled</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_source_set_priority</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_source_set_userdata</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_source_set_description</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry project='man-pages'><refentrytitle>waitid</refentrytitle><manvolnum>2</manvolnum></citerefentry>
|
||||
</para>
|
||||
</refsect1>
|
||||
|
||||
</refentry>
|
@ -275,6 +275,7 @@
|
||||
<citerefentry><refentrytitle>sd_event_add_time</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_add_signal</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_add_child</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_add_inotify</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_add_defer</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_source_set_enabled</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_source_set_priority</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
|
@ -198,6 +198,7 @@
|
||||
<citerefentry><refentrytitle>sd_event_add_io</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_add_time</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_add_child</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_add_inotify</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_add_defer</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_source_set_enabled</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_source_set_description</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
|
@ -288,6 +288,7 @@
|
||||
<citerefentry><refentrytitle>sd_event_add_io</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_add_signal</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_add_child</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_add_inotify</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_add_defer</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_source_set_enabled</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sd_event_source_set_priority</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
|
@ -108,6 +108,12 @@
|
||||
particular event sources do not starve or dominate the event
|
||||
loop.</para>
|
||||
|
||||
<para>The priority of event sources may be changed at any time of their lifetime, with the exception of inotify
|
||||
event sources (i.e. those created with
|
||||
<citerefentry><refentrytitle>sd_event_add_inotify</refentrytitle><manvolnum>3</manvolnum></citerefentry>) whose
|
||||
priority may only be changed in the time between their initial creation and the first subsequent event loop
|
||||
iteration.</para>
|
||||
|
||||
<para><function>sd_event_source_get_priority()</function> may be
|
||||
used to query the current priority assigned to the event source
|
||||
object <parameter>source</parameter>.</para>
|
||||
|
@ -1463,3 +1463,27 @@ bool in_utc_timezone(void) {
|
||||
|
||||
return timezone == 0 && daylight == 0;
|
||||
}
|
||||
|
||||
int time_change_fd(void) {
|
||||
|
||||
/* We only care for the cancellation event, hence we set the timeout to the latest possible value. */
|
||||
static const struct itimerspec its = {
|
||||
.it_value.tv_sec = TIME_T_MAX,
|
||||
};
|
||||
|
||||
_cleanup_close_ int fd;
|
||||
|
||||
assert_cc(sizeof(time_t) == sizeof(TIME_T_MAX));
|
||||
|
||||
/* Uses TFD_TIMER_CANCEL_ON_SET to get notifications whenever CLOCK_REALTIME makes a jump relative to
|
||||
* CLOCK_MONOTONIC. */
|
||||
|
||||
fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK|TFD_CLOEXEC);
|
||||
if (fd < 0)
|
||||
return -errno;
|
||||
|
||||
if (timerfd_settime(fd, TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET, &its, NULL) < 0)
|
||||
return -errno;
|
||||
|
||||
return TAKE_FD(fd);
|
||||
}
|
||||
|
@ -185,3 +185,5 @@ static inline usec_t usec_sub_signed(usec_t timestamp, int64_t delta) {
|
||||
#else
|
||||
#error "Yuck, time_t is neither 4 nor 8 bytes wide?"
|
||||
#endif
|
||||
|
||||
int time_change_fd(void);
|
||||
|
@ -108,6 +108,7 @@ static int manager_dispatch_user_lookup_fd(sd_event_source *source, int fd, uint
|
||||
static int manager_dispatch_jobs_in_progress(sd_event_source *source, usec_t usec, void *userdata);
|
||||
static int manager_dispatch_run_queue(sd_event_source *source, void *userdata);
|
||||
static int manager_dispatch_sigchld(sd_event_source *source, void *userdata);
|
||||
static int manager_dispatch_timezone_change(sd_event_source *source, const struct inotify_event *event, void *userdata);
|
||||
static int manager_run_environment_generators(Manager *m);
|
||||
static int manager_run_generators(Manager *m);
|
||||
|
||||
@ -352,35 +353,27 @@ static void manager_close_idle_pipe(Manager *m) {
|
||||
static int manager_setup_time_change(Manager *m) {
|
||||
int r;
|
||||
|
||||
/* We only care for the cancellation event, hence we set the
|
||||
* timeout to the latest possible value. */
|
||||
struct itimerspec its = {
|
||||
.it_value.tv_sec = TIME_T_MAX,
|
||||
};
|
||||
|
||||
assert(m);
|
||||
assert_cc(sizeof(time_t) == sizeof(TIME_T_MAX));
|
||||
|
||||
if (m->test_run_flags)
|
||||
return 0;
|
||||
|
||||
/* Uses TFD_TIMER_CANCEL_ON_SET to get notifications whenever
|
||||
* CLOCK_REALTIME makes a jump relative to CLOCK_MONOTONIC */
|
||||
m->time_change_event_source = sd_event_source_unref(m->time_change_event_source);
|
||||
m->time_change_fd = safe_close(m->time_change_fd);
|
||||
|
||||
m->time_change_fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK|TFD_CLOEXEC);
|
||||
m->time_change_fd = time_change_fd();
|
||||
if (m->time_change_fd < 0)
|
||||
return log_error_errno(errno, "Failed to create timerfd: %m");
|
||||
|
||||
if (timerfd_settime(m->time_change_fd, TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET, &its, NULL) < 0) {
|
||||
log_debug_errno(errno, "Failed to set up TFD_TIMER_CANCEL_ON_SET, ignoring: %m");
|
||||
m->time_change_fd = safe_close(m->time_change_fd);
|
||||
return 0;
|
||||
}
|
||||
return log_error_errno(m->time_change_fd, "Failed to create timer change timer fd: %m");
|
||||
|
||||
r = sd_event_add_io(m->event, &m->time_change_event_source, m->time_change_fd, EPOLLIN, manager_dispatch_time_change_fd, m);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create time change event source: %m");
|
||||
|
||||
/* Schedule this slightly earlier than the .timer event sources */
|
||||
r = sd_event_source_set_priority(m->time_change_event_source, SD_EVENT_PRIORITY_NORMAL-1);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set priority of time change event sources: %m");
|
||||
|
||||
(void) sd_event_source_set_description(m->time_change_event_source, "manager-time-change");
|
||||
|
||||
log_debug("Set up TFD_TIMER_CANCEL_ON_SET timerfd.");
|
||||
@ -388,6 +381,70 @@ static int manager_setup_time_change(Manager *m) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int manager_read_timezone_stat(Manager *m) {
|
||||
struct stat st;
|
||||
bool changed;
|
||||
|
||||
assert(m);
|
||||
|
||||
/* Read the current stat() data of /etc/localtime so that we detect changes */
|
||||
if (lstat("/etc/localtime", &st) < 0) {
|
||||
log_debug_errno(errno, "Failed to stat /etc/localtime, ignoring: %m");
|
||||
changed = m->etc_localtime_accessible;
|
||||
m->etc_localtime_accessible = false;
|
||||
} else {
|
||||
usec_t k;
|
||||
|
||||
k = timespec_load(&st.st_mtim);
|
||||
changed = !m->etc_localtime_accessible || k != m->etc_localtime_mtime;
|
||||
|
||||
m->etc_localtime_mtime = k;
|
||||
m->etc_localtime_accessible = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
static int manager_setup_timezone_change(Manager *m) {
|
||||
_cleanup_(sd_event_source_unrefp) sd_event_source *new_event = NULL;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
|
||||
if (m->test_run_flags != 0)
|
||||
return 0;
|
||||
|
||||
/* We watch /etc/localtime for three events: change of the link count (which might mean removal from /etc even
|
||||
* though another link might be kept), renames, and file close operations after writing. Note we don't bother
|
||||
* with IN_DELETE_SELF, as that would just report when the inode is removed entirely, i.e. after the link count
|
||||
* went to zero and all fds to it are closed.
|
||||
*
|
||||
* Note that we never follow symlinks here. This is a simplification, but should cover almost all cases
|
||||
* correctly.
|
||||
*
|
||||
* Note that we create the new event source first here, before releasing the old one. This should optimize
|
||||
* behaviour as this way sd-event can reuse the old watch in case the inode didn't change. */
|
||||
|
||||
r = sd_event_add_inotify(m->event, &new_event, "/etc/localtime",
|
||||
IN_ATTRIB|IN_MOVE_SELF|IN_CLOSE_WRITE|IN_DONT_FOLLOW, manager_dispatch_timezone_change, m);
|
||||
if (r == -ENOENT) /* If the file doesn't exist yet, subscribe to /etc instead, and wait until it is created
|
||||
* either by O_CREATE or by rename() */
|
||||
r = sd_event_add_inotify(m->event, &new_event, "/etc",
|
||||
IN_CREATE|IN_MOVED_TO|IN_ONLYDIR, manager_dispatch_timezone_change, m);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create timezone change event source: %m");
|
||||
|
||||
/* Schedule this slightly earlier than the .timer event sources */
|
||||
r = sd_event_source_set_priority(new_event, SD_EVENT_PRIORITY_NORMAL-1);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set priority of timezone change event sources: %m");
|
||||
|
||||
sd_event_source_unref(m->timezone_change_event_source);
|
||||
m->timezone_change_event_source = TAKE_PTR(new_event);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int enable_special_signals(Manager *m) {
|
||||
_cleanup_close_ int fd = -1;
|
||||
|
||||
@ -772,6 +829,14 @@ int manager_new(UnitFileScope scope, unsigned test_run_flags, Manager **_m) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = manager_read_timezone_stat(m);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = manager_setup_timezone_change(m);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = manager_setup_sigchld_event_source(m);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@ -1213,6 +1278,7 @@ Manager* manager_free(Manager *m) {
|
||||
sd_event_source_unref(m->notify_event_source);
|
||||
sd_event_source_unref(m->cgroups_agent_event_source);
|
||||
sd_event_source_unref(m->time_change_event_source);
|
||||
sd_event_source_unref(m->timezone_change_event_source);
|
||||
sd_event_source_unref(m->jobs_in_progress_event_source);
|
||||
sd_event_source_unref(m->run_queue_event_source);
|
||||
sd_event_source_unref(m->user_lookup_event_source);
|
||||
@ -2558,10 +2624,7 @@ static int manager_dispatch_time_change_fd(sd_event_source *source, int fd, uint
|
||||
LOG_MESSAGE("Time has been changed"));
|
||||
|
||||
/* Restart the watch */
|
||||
m->time_change_event_source = sd_event_source_unref(m->time_change_event_source);
|
||||
m->time_change_fd = safe_close(m->time_change_fd);
|
||||
|
||||
manager_setup_time_change(m);
|
||||
(void) manager_setup_time_change(m);
|
||||
|
||||
HASHMAP_FOREACH(u, m->units, i)
|
||||
if (UNIT_VTABLE(u)->time_change)
|
||||
@ -2570,6 +2633,41 @@ static int manager_dispatch_time_change_fd(sd_event_source *source, int fd, uint
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int manager_dispatch_timezone_change(
|
||||
sd_event_source *source,
|
||||
const struct inotify_event *e,
|
||||
void *userdata) {
|
||||
|
||||
Manager *m = userdata;
|
||||
int changed;
|
||||
Iterator i;
|
||||
Unit *u;
|
||||
|
||||
assert(m);
|
||||
|
||||
log_debug("inotify event for /etc/localtime");
|
||||
|
||||
changed = manager_read_timezone_stat(m);
|
||||
if (changed < 0)
|
||||
return changed;
|
||||
if (!changed)
|
||||
return 0;
|
||||
|
||||
/* Something changed, restart the watch, to ensure we watch the new /etc/localtime if it changed */
|
||||
(void) manager_setup_timezone_change(m);
|
||||
|
||||
/* Read the new timezone */
|
||||
tzset();
|
||||
|
||||
log_debug("Timezone has been changed (now: %s).", tzname[daylight]);
|
||||
|
||||
HASHMAP_FOREACH(u, m->units, i)
|
||||
if (UNIT_VTABLE(u)->timezone_change)
|
||||
UNIT_VTABLE(u)->timezone_change(u);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int manager_dispatch_idle_pipe_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) {
|
||||
Manager *m = userdata;
|
||||
|
||||
|
@ -170,6 +170,8 @@ struct Manager {
|
||||
int time_change_fd;
|
||||
sd_event_source *time_change_event_source;
|
||||
|
||||
sd_event_source *timezone_change_event_source;
|
||||
|
||||
sd_event_source *jobs_in_progress_event_source;
|
||||
|
||||
int user_lookup_fds[2];
|
||||
@ -250,6 +252,10 @@ struct Manager {
|
||||
|
||||
unsigned gc_marker;
|
||||
|
||||
/* The stat() data the last time we saw /etc/localtime */
|
||||
usec_t etc_localtime_mtime;
|
||||
bool etc_localtime_accessible:1;
|
||||
|
||||
/* Flags */
|
||||
ManagerExitCode exit_code:5;
|
||||
|
||||
|
@ -819,6 +819,18 @@ static void timer_time_change(Unit *u) {
|
||||
timer_enter_waiting(t, false);
|
||||
}
|
||||
|
||||
static void timer_timezone_change(Unit *u) {
|
||||
Timer *t = TIMER(u);
|
||||
|
||||
assert(u);
|
||||
|
||||
if (t->state != TIMER_WAITING)
|
||||
return;
|
||||
|
||||
log_unit_debug(u, "Timezone change, recalculating next elapse.");
|
||||
timer_enter_waiting(t, false);
|
||||
}
|
||||
|
||||
static const char* const timer_base_table[_TIMER_BASE_MAX] = {
|
||||
[TIMER_ACTIVE] = "OnActiveSec",
|
||||
[TIMER_BOOT] = "OnBootSec",
|
||||
@ -868,6 +880,7 @@ const UnitVTable timer_vtable = {
|
||||
|
||||
.reset_failed = timer_reset_failed,
|
||||
.time_change = timer_time_change,
|
||||
.timezone_change = timer_timezone_change,
|
||||
|
||||
.bus_vtable = bus_timer_vtable,
|
||||
.bus_set_property = bus_timer_set_property,
|
||||
|
@ -516,6 +516,9 @@ typedef struct UnitVTable {
|
||||
/* Called whenever CLOCK_REALTIME made a jump */
|
||||
void (*time_change)(Unit *u);
|
||||
|
||||
/* Called whenever /etc/localtime was modified */
|
||||
void (*timezone_change)(Unit *u);
|
||||
|
||||
/* Returns the next timeout of a unit */
|
||||
int (*get_timeout)(Unit *u, usec_t *timeout);
|
||||
|
||||
|
@ -563,4 +563,6 @@ global:
|
||||
sd_bus_open_system_with_description;
|
||||
sd_bus_slot_get_floating;
|
||||
sd_bus_slot_set_floating;
|
||||
sd_event_add_inotify;
|
||||
sd_event_source_get_inotify_mask;
|
||||
} LIBSYSTEMD_238;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -9,12 +9,19 @@
|
||||
|
||||
#include "sd-event.h"
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "fs-util.h"
|
||||
#include "log.h"
|
||||
#include "macro.h"
|
||||
#include "signal-util.h"
|
||||
#include "util.h"
|
||||
#include "parse-util.h"
|
||||
#include "process-util.h"
|
||||
#include "rm-rf.h"
|
||||
#include "signal-util.h"
|
||||
#include "stdio-util.h"
|
||||
#include "string-util.h"
|
||||
#include "util.h"
|
||||
|
||||
static int prepare_handler(sd_event_source *s, void *userdata) {
|
||||
log_info("preparing %c", PTR_TO_INT(userdata));
|
||||
@ -342,6 +349,142 @@ static void test_rtqueue(void) {
|
||||
sd_event_unref(e);
|
||||
}
|
||||
|
||||
#define CREATE_EVENTS_MAX (70000U)
|
||||
|
||||
struct inotify_context {
|
||||
bool delete_self_handler_called;
|
||||
unsigned create_called[CREATE_EVENTS_MAX];
|
||||
unsigned create_overflow;
|
||||
unsigned n_create_events;
|
||||
};
|
||||
|
||||
static void maybe_exit(sd_event_source *s, struct inotify_context *c) {
|
||||
unsigned n;
|
||||
|
||||
assert(s);
|
||||
assert(c);
|
||||
|
||||
if (!c->delete_self_handler_called)
|
||||
return;
|
||||
|
||||
for (n = 0; n < 3; n++) {
|
||||
unsigned i;
|
||||
|
||||
if (c->create_overflow & (1U << n))
|
||||
continue;
|
||||
|
||||
for (i = 0; i < c->n_create_events; i++)
|
||||
if (!(c->create_called[i] & (1U << n)))
|
||||
return;
|
||||
}
|
||||
|
||||
sd_event_exit(sd_event_source_get_event(s), 0);
|
||||
}
|
||||
|
||||
static int inotify_handler(sd_event_source *s, const struct inotify_event *ev, void *userdata) {
|
||||
struct inotify_context *c = userdata;
|
||||
const char *description;
|
||||
unsigned bit, n;
|
||||
|
||||
assert_se(sd_event_source_get_description(s, &description) >= 0);
|
||||
assert_se(safe_atou(description, &n) >= 0);
|
||||
|
||||
assert_se(n <= 3);
|
||||
bit = 1U << n;
|
||||
|
||||
if (ev->mask & IN_Q_OVERFLOW) {
|
||||
log_info("inotify-handler <%s>: overflow", description);
|
||||
c->create_overflow |= bit;
|
||||
} else if (ev->mask & IN_CREATE) {
|
||||
unsigned i;
|
||||
|
||||
log_info("inotify-handler <%s>: create on %s", description, ev->name);
|
||||
|
||||
if (!streq(ev->name, "sub")) {
|
||||
assert_se(safe_atou(ev->name, &i) >= 0);
|
||||
|
||||
assert_se(i < c->n_create_events);
|
||||
c->create_called[i] |= bit;
|
||||
}
|
||||
} else if (ev->mask & IN_DELETE) {
|
||||
log_info("inotify-handler <%s>: delete of %s", description, ev->name);
|
||||
assert_se(streq(ev->name, "sub"));
|
||||
} else
|
||||
assert_not_reached("unexpected inotify event");
|
||||
|
||||
maybe_exit(s, c);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int delete_self_handler(sd_event_source *s, const struct inotify_event *ev, void *userdata) {
|
||||
struct inotify_context *c = userdata;
|
||||
|
||||
if (ev->mask & IN_Q_OVERFLOW) {
|
||||
log_info("delete-self-handler: overflow");
|
||||
c->delete_self_handler_called = true;
|
||||
} else if (ev->mask & IN_DELETE_SELF) {
|
||||
log_info("delete-self-handler: delete-self");
|
||||
c->delete_self_handler_called = true;
|
||||
} else if (ev->mask & IN_IGNORED) {
|
||||
log_info("delete-self-handler: ignore");
|
||||
} else
|
||||
assert_not_reached("unexpected inotify event (delete-self)");
|
||||
|
||||
maybe_exit(s, c);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void test_inotify(unsigned n_create_events) {
|
||||
_cleanup_(rm_rf_physical_and_freep) char *p = NULL;
|
||||
sd_event_source *a = NULL, *b = NULL, *c = NULL, *d = NULL;
|
||||
struct inotify_context context = {
|
||||
.n_create_events = n_create_events,
|
||||
};
|
||||
sd_event *e = NULL;
|
||||
const char *q;
|
||||
unsigned i;
|
||||
|
||||
assert_se(sd_event_default(&e) >= 0);
|
||||
|
||||
assert_se(mkdtemp_malloc("/tmp/test-inotify-XXXXXX", &p) >= 0);
|
||||
|
||||
assert_se(sd_event_add_inotify(e, &a, p, IN_CREATE|IN_ONLYDIR, inotify_handler, &context) >= 0);
|
||||
assert_se(sd_event_add_inotify(e, &b, p, IN_CREATE|IN_DELETE|IN_DONT_FOLLOW, inotify_handler, &context) >= 0);
|
||||
assert_se(sd_event_source_set_priority(b, SD_EVENT_PRIORITY_IDLE) >= 0);
|
||||
assert_se(sd_event_source_set_priority(b, SD_EVENT_PRIORITY_NORMAL) >= 0);
|
||||
assert_se(sd_event_add_inotify(e, &c, p, IN_CREATE|IN_DELETE|IN_EXCL_UNLINK, inotify_handler, &context) >= 0);
|
||||
assert_se(sd_event_source_set_priority(c, SD_EVENT_PRIORITY_IDLE) >= 0);
|
||||
|
||||
assert_se(sd_event_source_set_description(a, "0") >= 0);
|
||||
assert_se(sd_event_source_set_description(b, "1") >= 0);
|
||||
assert_se(sd_event_source_set_description(c, "2") >= 0);
|
||||
|
||||
q = strjoina(p, "/sub");
|
||||
assert_se(touch(q) >= 0);
|
||||
assert_se(sd_event_add_inotify(e, &d, q, IN_DELETE_SELF, delete_self_handler, &context) >= 0);
|
||||
|
||||
for (i = 0; i < n_create_events; i++) {
|
||||
char buf[DECIMAL_STR_MAX(unsigned)+1];
|
||||
_cleanup_free_ char *z;
|
||||
|
||||
xsprintf(buf, "%u", i);
|
||||
assert_se(z = strjoin(p, "/", buf));
|
||||
|
||||
assert_se(touch(z) >= 0);
|
||||
}
|
||||
|
||||
assert_se(unlink(q) >= 0);
|
||||
|
||||
assert_se(sd_event_loop(e) >= 0);
|
||||
|
||||
sd_event_source_unref(a);
|
||||
sd_event_source_unref(b);
|
||||
sd_event_source_unref(c);
|
||||
sd_event_source_unref(d);
|
||||
|
||||
sd_event_unref(e);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
|
||||
log_set_max_level(LOG_DEBUG);
|
||||
@ -351,5 +494,8 @@ int main(int argc, char *argv[]) {
|
||||
test_sd_event_now();
|
||||
test_rtqueue();
|
||||
|
||||
test_inotify(100); /* should work without overflow */
|
||||
test_inotify(33000); /* should trigger a q overflow */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include <inttypes.h>
|
||||
#include <signal.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <sys/inotify.h>
|
||||
#include <sys/signalfd.h>
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
@ -78,6 +79,7 @@ typedef int (*sd_event_child_handler_t)(sd_event_source *s, const siginfo_t *si,
|
||||
#else
|
||||
typedef void* sd_event_child_handler_t;
|
||||
#endif
|
||||
typedef int (*sd_event_inotify_handler_t)(sd_event_source *s, const struct inotify_event *event, void *userdata);
|
||||
|
||||
int sd_event_default(sd_event **e);
|
||||
|
||||
@ -89,6 +91,7 @@ int sd_event_add_io(sd_event *e, sd_event_source **s, int fd, uint32_t events, s
|
||||
int sd_event_add_time(sd_event *e, sd_event_source **s, clockid_t clock, uint64_t usec, uint64_t accuracy, sd_event_time_handler_t callback, void *userdata);
|
||||
int sd_event_add_signal(sd_event *e, sd_event_source **s, int sig, sd_event_signal_handler_t callback, void *userdata);
|
||||
int sd_event_add_child(sd_event *e, sd_event_source **s, pid_t pid, int options, sd_event_child_handler_t callback, void *userdata);
|
||||
int sd_event_add_inotify(sd_event *e, sd_event_source **s, const char *path, uint32_t mask, sd_event_inotify_handler_t callback, void *userdata);
|
||||
int sd_event_add_defer(sd_event *e, sd_event_source **s, sd_event_handler_t callback, void *userdata);
|
||||
int sd_event_add_post(sd_event *e, sd_event_source **s, sd_event_handler_t callback, void *userdata);
|
||||
int sd_event_add_exit(sd_event *e, sd_event_source **s, sd_event_handler_t callback, void *userdata);
|
||||
@ -139,6 +142,7 @@ int sd_event_source_set_time_accuracy(sd_event_source *s, uint64_t usec);
|
||||
int sd_event_source_get_time_clock(sd_event_source *s, clockid_t *clock);
|
||||
int sd_event_source_get_signal(sd_event_source *s);
|
||||
int sd_event_source_get_child_pid(sd_event_source *s, pid_t *pid);
|
||||
int sd_event_source_get_inotify_mask(sd_event_source *s, uint32_t *ret);
|
||||
|
||||
/* Define helpers so that __attribute__((cleanup(sd_event_unrefp))) and similar may be used. */
|
||||
_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_event, sd_event_unref);
|
||||
|
@ -115,16 +115,15 @@ static int inotify_handler(sd_event_source *s,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int clock_state_update(ClockState *sp,
|
||||
sd_event *event) {
|
||||
static const struct itimerspec its = {
|
||||
.it_value.tv_sec = TIME_T_MAX,
|
||||
};
|
||||
int r;
|
||||
struct timex tx = {};
|
||||
static int clock_state_update(
|
||||
ClockState *sp,
|
||||
sd_event *event) {
|
||||
|
||||
char buf[MAX((size_t)FORMAT_TIMESTAMP_MAX, STRLEN("unrepresentable"))];
|
||||
usec_t t;
|
||||
struct timex tx = {};
|
||||
const char * ts;
|
||||
usec_t t;
|
||||
int r;
|
||||
|
||||
clock_state_release_timerfd(sp);
|
||||
|
||||
@ -151,19 +150,14 @@ static int clock_state_update(ClockState *sp,
|
||||
* it synchronized. When an NTP source is selected it sets the clock again with clock_adjtime(2) which marks it
|
||||
* synchronized and also touches /run/systemd/timesync/synchronized which covers the case when the clock wasn't
|
||||
* "set". */
|
||||
r = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK | TFD_CLOEXEC);
|
||||
|
||||
r = time_change_fd();
|
||||
if (r < 0) {
|
||||
log_error_errno(errno, "Failed to create timerfd: %m");
|
||||
log_error_errno(r, "Failed to create timerfd: %m");
|
||||
goto finish;
|
||||
}
|
||||
sp->timerfd_fd = r;
|
||||
|
||||
r = timerfd_settime(sp->timerfd_fd, TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET, &its, NULL);
|
||||
if (r < 0) {
|
||||
log_error_errno(errno, "Failed to set timerfd conditions: %m");
|
||||
goto finish;
|
||||
}
|
||||
|
||||
r = adjtimex(&tx);
|
||||
if (r < 0) {
|
||||
log_error_errno(errno, "Failed to read adjtimex state: %m");
|
||||
|
@ -224,11 +224,6 @@ static int manager_clock_watch(sd_event_source *source, int fd, uint32_t revents
|
||||
|
||||
/* wake up when the system time changes underneath us */
|
||||
static int manager_clock_watch_setup(Manager *m) {
|
||||
|
||||
struct itimerspec its = {
|
||||
.it_value.tv_sec = TIME_T_MAX
|
||||
};
|
||||
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
@ -236,12 +231,9 @@ static int manager_clock_watch_setup(Manager *m) {
|
||||
m->event_clock_watch = sd_event_source_unref(m->event_clock_watch);
|
||||
safe_close(m->clock_watch_fd);
|
||||
|
||||
m->clock_watch_fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK|TFD_CLOEXEC);
|
||||
m->clock_watch_fd = time_change_fd();
|
||||
if (m->clock_watch_fd < 0)
|
||||
return log_error_errno(errno, "Failed to create timerfd: %m");
|
||||
|
||||
if (timerfd_settime(m->clock_watch_fd, TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET, &its, NULL) < 0)
|
||||
return log_error_errno(errno, "Failed to set up timerfd: %m");
|
||||
return log_error_errno(m->clock_watch_fd, "Failed to create timerfd: %m");
|
||||
|
||||
r = sd_event_add_io(m->event, &m->event_clock_watch, m->clock_watch_fd, EPOLLIN, manager_clock_watch, m);
|
||||
if (r < 0)
|
||||
|
Loading…
Reference in New Issue
Block a user