Commit Graph

439 Commits

Author SHA1 Message Date
Philip Withnall
1314ff93fc glib: Drop unnecessary volatile qualifiers from internal variables
These variables were already (correctly) accessed atomically. The
`volatile` qualifier doesn’t help with that.

Signed-off-by: Philip Withnall <pwithnall@endlessos.org>

Helps: #600
2020-11-20 14:40:19 +00:00
Claudio Saavedra
96ccf06d3d gmain: g_main_context_check() can skip updating polled FD sources
If there is a file descriptor source that has a lower priority
than the one for sources that are going to be dispatched,
all subsequent file descriptor sources (internally sorted by
file descriptor identifier) do not get an update in their GPollRec
and later on wrong sources can be dispatched.

Fix this by first finding the first GPollRec that matches the current
GPollFD, instead of relying on it to be the current one. At
the same time, document the assumptions about the ordering of the
file descriptor records and array and make explicit in the documentation
that the array needs to be passed to g_main_context_check() as it was
received from g_main_context_query().

Added a new test that reproduces the bug by creating two file
descriptor sources and an idle one. Since the first
file descriptor created has a lower identifier and a low priority,
the second one is not dispatched even when it has the same, higher,
priority as the idle source. After fixing this bug, both
higher priority sources are dispatched as expected.

While this patch was written independently, a similar fix for this
bug was first submitted by Eugene M in GNOME/glib!562. Having a
second fix that basically does the same is a reassurance that we
are in the right here.

Fixes #1592
2020-10-26 13:08:01 +00:00
Sebastian Dröge
c686e1a048 Add various missing nullable annotations 2020-10-19 13:28:46 +03:00
Benjamin Berg
bf814a9aa3 gmain: Fix possible locking issue in source unref
When unref'ing child sources, the lock is already held. But instead of
passing TRUE to g_source_unref_internal it currently passes whether the
lock was already held outside of the current invocation. Just pass TRUE
to fix this possible issue.
2020-10-13 15:12:37 +02:00
Philip Withnall
a82556c98d gmain: Fix minor typo in documentation
Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
2020-10-05 11:41:40 +01:00
Philip Withnall
c60d6599c9 Merge branch 'mach_monotonic_time_use_timebase_struct' into 'master'
gmain: fix fetching timebase on non-Intel Darwin

Closes #858

See merge request GNOME/glib!1566
2020-07-26 13:22:48 +00:00
Nirbheek Chauhan
c3c2c31335 glib: Use g_getenv everywhere instead of getenv
`getenv()` doesn't work well on Windows, f.ex., it can't fetch env
vars set with `SetEnvironmentVariable()`. This also means that it
doesn't work at all when targeting UWP since that's the only way to
set env vars in that case.
2020-07-23 13:57:59 +05:30
holin
eb3409a9c9
gmain: fix fetching timebase on non-Intel Darwin
Co-authored-by: Misty De Meo <mistydemeo@gmail.com>
2020-07-13 10:25:20 -07:00
Philip Withnall
bfe161742c gmain: Add sysprof tracing to GMainContext and GSource
This allows you to see how long each `GMainContext` iteration and each
`GSource` `check`/`prepare`/`dispatch` takes. It provides more detail
than sysprof’s speedtrack plugin can provide, since it has access to
more internal GLib data.

Use it with `sysprof-cli`, for example:
```
sysprof-cli --use-trace-fd -- my-test-program
```

Signed-off-by: Philip Withnall <withnall@endlessm.com>
2020-07-07 11:17:10 +01:00
Philip Withnall
da948f7218 gmain: Fix use of atomic primitives with sig_atomic_t
It seems that `sig_atomic_t` is not the same width as `int` on FreeBSD,
which is causing CI failures:
```
 ../glib/gmain.c:5206:3: error: '_GStaticAssertCompileTimeAssertion_73' declared as an array with a negative size
  g_atomic_int_set (&any_unix_signal_pending, 0);
  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../glib/gatomic.h💯5: note: expanded from macro 'g_atomic_int_set'
    G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gint));                     \
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```

Fix that by only using `sig_atomic_t` if the code is *not* using atomic
primitives (i.e. in the fallback case). `sig_atomic_t` is only a typedef
around an integer type and is not magic. Its typedef is chosen by the
platform to be async-signal-safe (i.e. read or written in one instruction),
but not necessarily thread-safe.

Signed-off-by: Philip Withnall <withnall@endlessm.com>
2020-06-29 10:42:00 +01:00
Philip Withnall
253f5cda82 gmain: Access Unix signal handler state atomically
There are two variables which are used to pass state from the Unix
signal handler interrupt function to the rest of `gmain.c`. They are
currently defined as `sig_atomic_t`, which means that they are
guaranteed to be interrupt safe. However, it does not guarantee they are
thread-safe, and GLib attaches its signal handler interrupt function to
a worker thread.

Make them thread-safe using atomics. It’s not possible to use locks, as
pthread mutex functions are not signal-handler-safe. In particular, this
means we have to be careful not to end up using GLib’s fallback atomics
implementation, as that secretly uses a mutex. Better to be unsafe than
have a re-entrant call into `pthread_mutex_lock()` from a nested signal
handler.

This commit solves two problems:
 1. Writes to `any_unix_signal_pending` and `unix_signal_pending` could
    be delivered out of order to the worker thread which calls
    `dispatch_unix_signals()`, resulting in signals not being handled
    until the next iteration of that worker thread. This is a
    performance problem but not a correctness problem.
 2. Setting an element of `unix_signal_pending` from
    `g_unix_signal_handler()` and clearing it from
    `dispatch_unix_signals_unlocked()` (in the worker thread) could
    race, resulting in a signal emission being cleared without being
    handled. That’s a correctness problem.

Signed-off-by: Philip Withnall <withnall@endlessm.com>

Fixes: #1670
2020-06-19 10:49:14 +01:00
Sebastian Dröge
730a75fc8e GMainContext - Move mutex unlocking in destructor right before freeing the mutex
This does not have any behaviour changes but is cleaner. The mutex is
only unlocked now after all operations on the context are done and right
before freeing the mutex and the context itself.
2020-02-11 10:53:56 +02:00
Sebastian Dröge
aa20167d41 GMainContext - Fix memory leaks and memory corruption when freeing sources while freeing a context
Instead of destroying sources directly while freeing the context, and
potentially freeing them if this was the last reference to them, collect
new references of all sources in a separate list before and at the same
time invalidate their context so that they can't access it anymore. Only
once all sources have their context invalidated, destroy them while
still keeping a reference to them. Once all sources are destroyed we get
rid of the additional references and free them if nothing else keeps a
reference to them anymore.

This fixes a regression introduced by 26056558be in 2012.

The previous code that invalidated the context of each source and then
destroyed it before going to the next source without keeping an
additional reference caused memory leaks or memory corruption depending
on the order of the sources in the sources lists.

If a source was destroyed it might happen that this was the last
reference to this source, and it would then be freed. This would cause
the finalize function to be called, which might destroy and unref
another source and potentially free it. This other source would then
either
- go through the normal free logic and change the intern linked list
  between the sources, while other sources that are unreffed as part of
  the main context freeing would not. As such the list would be in an
  inconsistent state and we might dereference freed memory.
- go through the normal destroy and free logic but because the context
  pointer was already invalidated it would simply mark the source as
  destroyed without actually removing it from the context. This would
  then cause a memory leak because the reference owned by the context is
  not freed.

Fixes https://github.com/gtk-rs/glib/issues/583 while still keeping
https://bugzilla.gnome.org/show_bug.cgi?id=661767 fixes.
2020-02-11 10:53:56 +02:00
Sebastian Dröge
b06c48de75 GMainContext - Fix GSource iterator if iteration can modify the list
We first have to ref the next source and then unref the previous one.
This might be the last reference to the previous source, and freeing the
previous source might unref and free the next one which would then leave
use with a dangling pointer here.

Fixes https://gitlab.gnome.org/GNOME/glib/issues/2031
2020-02-11 10:53:52 +02:00
Sebastian Dröge
be1ec9004b Add guards against invalid GSources to public GSource API
Especially check for a valid reference count. This is possible now in
all cases because of the addition of the dispose function and makes
usage of already finalized/finalizing GSources more obvious than the
use-after-free that would otherwise happen.
2019-11-29 20:56:38 +01:00
Sebastian Dröge
0adf5cae35 Add g_source_set_dispose_function() for setting a dispose function for GSource
This allows GSource implementors to safely clear any other references to
the GSource while the GSource is still valid, unlike when doing the same
from the finalize function.

After the dispose function has run, it is valid for the reference count
of the GSource to be > 0 again to allow the case where another thread in
the mean-time got access to the GSource reference before the dispose
function was called.

This allows fixing a thread-safety issue in the GCancellable, GstBus and
various other GSource implementations.
2019-11-29 20:56:38 +01:00
Philip Withnall
ca4dace62b gmain: Clarify thread safety of some common GSource functions
See https://stackoverflow.com/q/58555626/2931197.

Signed-off-by: Philip Withnall <withnall@endlessm.com>
2019-10-25 12:13:31 +01:00
Philip Withnall
d9b30d47a6 gmain: Remove some redundant casts
These were introducing strict aliasing warnings. Remove them (in line
with other uses of `g_once_init_leave()`).

Signed-off-by: Philip Withnall <withnall@endlessm.com>
2019-10-07 16:29:26 +01:00
Thomas Haller
39dd2be538 gmain: use atomic operation instead of GMutex to access g_main_context_default()
I think it is wasteful to use a mutex every time the default main context
is accessed. Especially, as the default main context is used all the
time.
2019-10-07 13:22:30 +00:00
Philip Withnall
18a232be89 glib: Various minor scan-build fixes
These squash various warnings from `scan-build`. None of them are
legitimate bugs, but some of them do improve code readability a bit.

Signed-off-by: Philip Withnall <withnall@endlessm.com>

Helps: #1767
2019-09-05 13:51:27 +01:00
Philip Withnall
14b087ea18 gmain.c: Fix comment about Y2038 safety of g_get_real_time()
This comment was correct until commit adf1f98f62, when the `GTimeVal`
which the result was put into (introducing the Y2038-unsafety) was
dropped.

The adjustment and scaling of the `FILETIME` should not make it
Y2038-unsafe: the maximum `FILETIME` is 2^64-1. Subtracting the epoch
adjustment and dividing by 10 gives the timestamp 1833029933770955161,
which is in June 58086408216 (at just after 3am UTC). I think that’s
enough time to be going on with.

Signed-off-by: Philip Withnall <withnall@endlessm.com>

Helps: #1438
2019-07-29 12:27:29 +01:00
Philip Withnall
ec0cc39de0 gmain: Add deprecation ignore guards around other GTimeVal usage
Signed-off-by: Philip Withnall <withnall@endlessm.com>

Helps: #1438
2019-07-29 12:27:29 +01:00
Philip Withnall
fe760ba442 gmain: Swap implementations of g_get_current_time() + g_get_real_time()
The former is now deprecated, so it makes sense to base its
implementation on the latter, rather than the other way around.

This introduces no functional changes.

Signed-off-by: Philip Withnall <withnall@endlessm.com>

Helps: #1438
2019-07-29 12:27:29 +01:00
Philip Withnall
626b6f5ea7 gmain: Deprecate g_get_current_time() because it uses GTimeVal
GTimeVal is not year-2038-safe.

Signed-off-by: Philip Withnall <withnall@endlessm.com>

Helps: #1438
2019-07-29 12:27:29 +01:00
David Emett
ca47f3dfd5 gmain: Fix g_main_context_prepare priority annotation 2019-06-29 14:13:16 +01:00
Philip Withnall
71973c722a gmain: Clarify that g_source_destroy() doesn’t drop a reference
This always confuses people.

Signed-off-by: Philip Withnall <withnall@endlessm.com>
2019-06-07 11:26:47 +01:00
Sebastian Dröge
0f056ebea3 Use atomic reference counting for GSource
If attached to a context already it would use a mutex instead but at
least before that the reference counting is not thread-safe currently.
2019-05-28 18:46:18 +03:00
Philip Withnall
38de3e9dc3 docs: Use ‘look up’ as a verb, rather than the noun ‘lookup’
Another niggle fixed.

Signed-off-by: Philip Withnall <withnall@endlessm.com>
2019-04-26 12:12:31 +01:00
Emmanuel Fleury
81a4698c45 Fixing various warnings in glib/gmain.c
glib/gmain.c:480:1: error: missing initializer for field ‘closure_callback’ of ‘GSourceFuncs’ {aka ‘struct _GSourceFuncs’} [-Werror=missing-field-initializers]
 };
 ^
In file included from glib/giochannel.h:33,
                 from glib/glib.h:54,
                 from glib/glib-unix.h:33,
                 from glib/gmain.c:50:
glib/gmain.h:262:19: note: ‘closure_callback’ declared here
   GSourceFunc     closure_callback;
                   ^~~~~~~~~~~~~~~~
glib/gmain.c:491:1: error: missing initializer for field ‘closure_callback’ of ‘GSourceFuncs’ {aka ‘struct _GSourceFuncs’} [-Werror=missing-field-initializers]
 };
 ^
In file included from glib/giochannel.h:33,
                 from glib/glib.h:54,
                 from glib/glib-unix.h:33,
                 from glib/gmain.c:50:
glib/gmain.h:262:19: note: ‘closure_callback’ declared here
   GSourceFunc     closure_callback;
                   ^~~~~~~~~~~~~~~~
glib/gmain.c:499:1: error: missing initializer for field ‘closure_callback’ of ‘GSourceFuncs’ {aka ‘struct _GSourceFuncs’} [-Werror=missing-field-initializers]
 };
 ^
In file included from glib/giochannel.h:33,
                 from glib/glib.h:54,
                 from glib/glib-unix.h:33,
                 from glib/gmain.c:50:
glib/gmain.h:262:19: note: ‘closure_callback’ declared here
   GSourceFunc     closure_callback;
                   ^~~~~~~~~~~~~~~~
glib/gmain.c:507:1: error: missing initializer for field ‘closure_callback’ of ‘GSourceFuncs’ {aka ‘struct _GSourceFuncs’} [-Werror=missing-field-initializers]
 };
 ^
In file included from glib/giochannel.h:33,
                 from glib/glib.h:54,
                 from glib/glib-unix.h:33,
                 from glib/gmain.c:50:
glib/gmain.h:262:19: note: ‘closure_callback’ declared here
   GSourceFunc     closure_callback;
                   ^~~~~~~~~~~~~~~~
glib/gmain.c: In function ‘g_source_set_callback_indirect’:
glib/gmain.c:1615:68: error: suggest braces around empty body in an ‘if’ statement [-Werror=empty-body]
                                               callback_funcs->get));
                                                                    ^
2019-03-15 21:30:22 +01:00
Tomasz Miąsko
a3060bc84f gmain: Synchronize access to is_running flag of GMainLoop
Synchronize access to is_running field of GMainLoop to ensure that
g_main_loop_is_running is thread safe.
2019-02-22 18:08:34 +01:00
Philip Withnall
f6caeb6d1a gthread: Add g_private_set_alloc0() internal convenience API
This is a wrapper around g_private_set() which allocates the desired
amount of memory for the caller and calls g_private_set() on it.

This is intended to make it easier to suppress Valgrind warnings about
leaked memory, since g_private_set() is typically used to make one-time
per-thread allocations. We can now just add a blanket suppression rule
for any allocations inside g_private_set_alloc0().

Signed-off-by: Philip Withnall <withnall@endlessm.com>
2019-01-07 18:54:17 +00:00
Will Thompson
cdc2dd8eb1
gmain: clamp over-large timeouts
g_main_context_prepare() needs to calculate the timeout to pass to
poll(), expressed in milliseconds as a gint.  But since the ready time
for a GSource is represented by gint64 microseconds, it's possible that
it could be more than G_MAXINT * 1000 microseconds in the future, and so
can't be represented as a gint. This conversion to a narrower signed
type is implementation-defined, but there are two possible outcomes:

* the result is >= 0, in which case poll() will time out earlier than we
  might hope (with no adverse consequences besides an unwanted wakeup)
* the result is < 0, in which case, if there are no other sources,
  poll() will block forever

This is extremely unlikely to happen in practice, but can be avoided by
clamping the gint64 value, which we know to be positive, to G_MAXINT.

Thanks to Tomasz Miąsko for pointing this out on !496.
2018-11-27 10:36:20 +00:00
Will Thompson
4ff3734ff5
g_timeout_*_seconds: don't overflow for large intervals
Previously, the `guint interval` parameter, measured in seconds, was
multiplied by 1000 and stored in another `guint` field. For intervals
greater than (G_MAXUINT / 1000) seconds, this would overflow; the
timeout would fire much sooner than intended.

Since GTimeoutSource already keeps track of whether it was created at
millisecond or second resolution, always store the passed interval
directly. We later convert the interval to microseconds, stored in a
gint64, so can move the `* 1000` to there.

The eagle-eyed reader might notice that there is no obvious guarantee
that the source's expiration time in microseconds won't overflow the
gint64, but I don't think this is a new problem. Previously, the
monotonic time would have to reach (2 ** 63 - 2 ** 32) microseconds for
this overflow to occur; now it would have to reach approximately (2 **
63 - 2 ** 42) microseconds. Both of these are 292.47 millennia to 5
significant figures.

Fixes #1600.
2018-11-26 16:04:10 +00:00
Tomasz Miąsko
dd36cf3a8b gmain: Indicate atomic fields with a comment 2018-11-13 14:57:45 +01:00
Tomasz Miąsko
969b0e00b3 gmain: Guarantee handler dispatch after receiving UNIX signal
Guarantee that user signal callback is dispatched _after_ receiving a
signal as long as the handler expresses continued interest in receiving
such a notification.

Previously if a signal has been received during user callback dispatch
but before pending flag had been cleared then the signal would be
irrevocably lost.

This is a very useful guarantee to have in cases where signals are used
to signify a need for synchronization with external resources. For
example: reloading configuration file after SIGUSR1 or retrieving a
terminal size after SIGWINCH.
2018-11-13 14:57:27 +01:00
Tomasz Miąsko
9e652f94d2 gmain: Make GUnixSignalWatchSource pending field atomic
Ensure synchronization between prepare / check /dispatch of
GUnixSignalWatchSource and UNIX signal dispatcher by making operations
on `pending` field atomic.

Issue #1312.
2018-11-13 14:57:22 +01:00
Tomasz Miąsko
d2fd53df03 gmain: Make GChildWatchSource child_exited field atomic
Ensure synchronization between prepare / check of GChildWatchsource and
UNIX signal dispatcher by making operations on `child_exited` field
atomic. Use `child_exited` as publication flag for `child_status`.

Issue #1312.
2018-11-13 14:52:50 +01:00
Tomasz Miąsko
1c8f3c67c3 gmain: Remove redundant volatile from unix_signal_refcount
Acesss to unix_signal_refcount is protected by unix_signal_lock.
2018-11-13 14:52:50 +01:00
Philip Withnall
bfe5906b40 gmain: Clarify that g_source_set_callback() is safe on attached sources
g_source_set_callback() and g_source_set_callback_indirect() are both
safe to call zero or more times on attached sources. The change in
callback will take effect the next time the source is dispatched, after
the set_callback() call returns (it could block due to locking).

https://gitlab.gnome.org/GNOME/glib/issues/827
2018-10-29 22:10:33 +00:00
Philip Withnall
0ea541171d Merge branch '903-deprecate-main-context-wait' into 'master'
gmain: Officially deprecate g_main_context_wait()

Closes #903

See merge request GNOME/glib!139
2018-06-28 16:59:39 +00:00
Philip Withnall
7a34e396ae gmain: Officially deprecate g_main_context_wait()
It’s been de-facto deprecated for a long time, due to emitting a
critical warning when used in a non-internal context. Make that official
in the documentation and with a deprecation annotation.

Split the implementation into an internal helper and an external
wrapper, so the two remaining internal uses don’t emit deprecation
warnings.

Signed-off-by: Philip Withnall <withnall@endlessm.com>

https://gitlab.gnome.org/GNOME/glib/issues/903
2018-06-26 09:30:19 +01:00
Philip Withnall
208a6e815a gmain: Add names to various GSources constructed in GLib
For the purposes of debugging, it is quite useful for every GSource to
have a name set. Ensure that any GSource we construct inside GLib has a
name set. For GSources which are then returned to the caller, this name
can then be overridden with something even more useful by the caller.

Since this data is only used for debugging, avoid doing any allocations
for it; just use static strings.

https://gitlab.gnome.org/GNOME/glib/issues/1175
2018-06-26 09:25:39 +01:00
Philip Withnall
515dac258c gmain: Clarify documentation for g_source_get_id()
It’s good to know *which* GMainContext is used to determine the ID, and
the preconditions for calling this method.

Using wording suggested by Emmanuele Bassi <ebassi@gmail.com>.

Signed-off-by: Philip Withnall <withnall@endlessm.com>
2018-06-25 13:46:43 +01:00
Will Thompson
039fa6897b
Add G_SOURCE_FUNC cast macro which suppresses -Wcast-function-type
This is the workaround suggested by
https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wcast-function-type

This warning is not enabled by default during the GLib build, but
applications may want to opt into it.
2018-06-14 10:11:12 +01:00
Will Thompson
0f7c196c21
g_clear_handle_id: don't accept NULL clear_func
Fixes #1401.
2018-06-04 13:11:49 +01:00
Philip Withnall
9b4c50f63d all: Remove trailing newlines from g_message()/g_warning()/g_error()s
All those logging functions already add a newline to any message they
print, so there’s no need to add a trailing newline in the message
passed to them.

Signed-off-by: Philip Withnall <withnall@endlessm.com>
Reviewed-by: nobody
2018-04-27 16:46:19 +01:00
Philip Withnall
9dd8e833ef gmain: Fix some minor typos in the documentation
Signed-off-by: Philip Withnall <withnall@endlessm.com>
Reviewed-by: nobody
2018-03-26 14:48:12 +01:00
Philip Withnall
1e6803be3b gmain: Partial revert of recent wakeup changes to gmain.c
This reverts the following commits (but keeps the other recent changes
to gmain.c):
 • e4ee3079c Do not wake up main loop if change is from same thread
 • 208702404 main: Create a helper function for "owner wakeup" optimization
 • 0c0469b56 gmain: Signal wakeups if context has never been acquired as well
 • 9ba95e25b gmain: only signal GWakeup right before or during a blocking poll

Some combination of them is causing problems with LibreOffice and/or
WebKit, and the safest thing to do at the moment is revert them all
until we work out what’s going on. The previous revert (4976e8109) was
not sufficient (it fixed WebKit, but re-broken LibreOffice).

By reverting, we gain some spurious wakeups, but avoid dropping
necessary wakeups, which is presumably what’s causing problems in the
other modules.

Signed-off-by: Philip Withnall <withnall@endlessm.com>

https://bugzilla.gnome.org/show_bug.cgi?id=761102
2018-01-18 11:31:08 +00:00
Simon McVittie
7f3bfcb891 cancellable: Don't assert if finalization races with cancellation
Commit 281e3010 narrowed the race between GCancellable::cancelled and
GCancellableSource's finalize(), but did not prevent it: there was
nothing to stop cancellation from occurring after the refcount drops
to 0, but before g_source_unref_internal() bumps it back up to 1 to
run finalize().

GCancellable cannot be expected to detect that situation, because the
only way it has to detect last-unref is finalize(), but in that
situation finalize() hasn't happened yet.

Instead of detecting last-unref, relax the precondition a little
to make it detect finalization: priv is only poisoned (set to NULL)
after the finalize() function has been called, so we can assume that
GCancellable has already seen finalize() by then.

Signed-off-by: Simon McVittie <smcv@collabora.com>
Bug: https://bugzilla.gnome.org/show_bug.cgi?id=791754
Bug-Debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=884654
2018-01-05 20:42:06 +00:00
Simon McVittie
a4686b8ea1 g_source_set_ready_time: Move no-op fast-path under the lock
If we don't take the lock, then we don't have the necessary
"happens before" relationships to avoid this situation:

* source->priv->ready_time was equal to ready_time until recently
* another thread has set source->priv->ready_time to a different value
* that write hasn't become visible to this thread yet
* result: we should reset the ready_time, but we don't

Signed-off-by: Simon McVittie <smcv@collabora.com>
Bug: https://bugzilla.gnome.org/show_bug.cgi?id=791754
Bug-Debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=884654
2018-01-05 20:42:06 +00:00