diff --git a/Base/usr/share/man/man2/adjtime.md b/Base/usr/share/man/man2/adjtime.md new file mode 100644 index 0000000000..29b315426a --- /dev/null +++ b/Base/usr/share/man/man2/adjtime.md @@ -0,0 +1,33 @@ +## Name + +adjtime - gradually adjust system clock + +## Synopsis + +```**c++ +#include + +int adjtime(const struct timeval* delta, struct timeval* old_delta); +``` + +## Description + +`adjtime()` gradually increments the system time by `delta`, if it is non-null. + +Serenity OS slows down or speeds up the system clock by at most 1%, so adjusting the time by N seconds takes 100 * n seconds to complete. + +Calling `settimeofday()` or `clock_settime()` cancels in-progress time adjustments done by `adjtime`. + +If `delta` is not null, `adjtime` can only called by the superuser. + +If `old_delta` is not null, it returns the currently remaining time adjustment. Querying the remaining time adjustment does not need special permissions. + +## Pledge + +In pledged programs, the `settime` promise is required when `delta` is not null. + +## Errors + +* `EFAULT`: `delta` and/or `old_delta` are not null and not in readable memory. +* `EINVAL`: `delta` is not null and has a `tv_nsec` field that's less than 0 or larger or equal to `10^6`. Negative deltas should have a negative `tv_sec` field but a `tv_nsec` that's larger or equal zero. For example, a delta of -0.5 s is represented by `{-1, 500'000}`. +* `EPERM`: `delta` is not null but geteuid() is not 0. diff --git a/Kernel/API/Syscall.h b/Kernel/API/Syscall.h index 54fb533f13..897d05d64d 100644 --- a/Kernel/API/Syscall.h +++ b/Kernel/API/Syscall.h @@ -193,7 +193,8 @@ namespace Kernel { S(recvfd) \ S(sysconf) \ S(set_process_name) \ - S(disown) + S(disown) \ + S(adjtime) namespace Syscall { diff --git a/Kernel/Process.h b/Kernel/Process.h index 743d654e8b..f62671fd74 100644 --- a/Kernel/Process.h +++ b/Kernel/Process.h @@ -241,6 +241,7 @@ public: int sys$fchdir(int fd); int sys$sleep(unsigned seconds); int sys$usleep(useconds_t usec); + int sys$adjtime(Userspace, Userspace); int sys$gettimeofday(Userspace); int sys$clock_gettime(clockid_t, Userspace); int sys$clock_settime(clockid_t, Userspace); diff --git a/Kernel/Syscalls/clock.cpp b/Kernel/Syscalls/clock.cpp index 3ee0c85339..e7d40c5be3 100644 --- a/Kernel/Syscalls/clock.cpp +++ b/Kernel/Syscalls/clock.cpp @@ -24,6 +24,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include #include #include @@ -119,6 +120,35 @@ int Process::sys$clock_nanosleep(Userspace user_delta, Userspace user_old_delta) +{ + if (user_old_delta) { + timespec old_delta_ts = TimeManagement::the().remaining_epoch_time_adjustment(); + timeval old_delta; + timespec_to_timeval(old_delta_ts, old_delta); + if (!copy_to_user(user_old_delta, &old_delta)) + return -EFAULT; + } + + if (user_delta) { + REQUIRE_PROMISE(settime); + if (!is_superuser()) + return -EPERM; + timeval delta; + if (!copy_from_user(&delta, user_delta)) + return -EFAULT; + + if (delta.tv_usec < 0 || delta.tv_usec >= 1'000'000) + return -EINVAL; + + timespec delta_ts; + timeval_to_timespec(delta, delta_ts); + TimeManagement::the().set_remaining_epoch_time_adjustment(delta_ts); + } + + return 0; +} + int Process::sys$gettimeofday(Userspace user_tv) { REQUIRE_PROMISE(stdio); diff --git a/Kernel/Time/TimeManagement.cpp b/Kernel/Time/TimeManagement.cpp index 4e1578cc1e..68dedaea66 100644 --- a/Kernel/Time/TimeManagement.cpp +++ b/Kernel/Time/TimeManagement.cpp @@ -25,6 +25,7 @@ */ #include +#include #include #include #include @@ -59,6 +60,7 @@ void TimeManagement::set_epoch_time(timespec ts) { InterruptDisabler disabler; m_epoch_time = ts; + m_remaining_epoch_time_adjustment = { 0, 0 }; } timespec TimeManagement::epoch_time() const @@ -256,7 +258,21 @@ void TimeManagement::increment_time_since_boot(const RegisterState&) { ASSERT(!m_time_keeper_timer.is_null()); - timespec epoch_tick = { .tv_sec = 0, .tv_nsec = 1'000'000 }; // FIXME: Don't assume that one tick is 1 ms. + // Compute time adjustment for adjtime. Let the clock run up to 1% fast or slow. + // That way, adjtime can adjust up to 36 seconds per hour, without time getting very jumpy. + // Once we have a smarter NTP service that also adjusts the frequency instead of just slewing time, maybe we can lower this. + constexpr long NanosPerTick = 1'000'000; // FIXME: Don't assume that one tick is 1 ms. + constexpr time_t MaxSlewNanos = NanosPerTick / 100; + static_assert(MaxSlewNanos < NanosPerTick); + + // Clamp twice, to make sure intermediate fits into a long. + long slew_nanos = clamp(clamp(m_remaining_epoch_time_adjustment.tv_sec, (time_t)-1, (time_t)1) * 1'000'000'000 + m_remaining_epoch_time_adjustment.tv_nsec, -MaxSlewNanos, MaxSlewNanos); + timespec slew_nanos_ts; + timespec_sub({ 0, slew_nanos }, { 0, 0 }, slew_nanos_ts); // Normalize tv_nsec to be positive. + timespec_sub(m_remaining_epoch_time_adjustment, slew_nanos_ts, m_remaining_epoch_time_adjustment); + + timespec epoch_tick = { .tv_sec = 0, .tv_nsec = NanosPerTick }; + epoch_tick.tv_nsec += slew_nanos; // No need for timespec_add(), guaranteed to be in range. timespec_add(m_epoch_time, epoch_tick, m_epoch_time); if (++m_ticks_this_second >= m_time_keeper_timer->ticks_per_second()) { diff --git a/Kernel/Time/TimeManagement.h b/Kernel/Time/TimeManagement.h index f3a3bb0188..b6c268e3af 100644 --- a/Kernel/Time/TimeManagement.h +++ b/Kernel/Time/TimeManagement.h @@ -62,6 +62,9 @@ public: static timeval now_as_timeval(); + timespec remaining_epoch_time_adjustment() const { return m_remaining_epoch_time_adjustment; } + void set_remaining_epoch_time_adjustment(const timespec& adjustment) { m_remaining_epoch_time_adjustment = adjustment; } + private: bool probe_and_set_legacy_hardware_timers(); bool probe_and_set_non_legacy_hardware_timers(); @@ -73,6 +76,7 @@ private: u32 m_ticks_this_second { 0 }; u32 m_seconds_since_boot { 0 }; timespec m_epoch_time { 0, 0 }; + timespec m_remaining_epoch_time_adjustment { 0, 0 }; RefPtr m_system_timer; RefPtr m_time_keeper_timer; }; diff --git a/Libraries/LibC/sys/time.h b/Libraries/LibC/sys/time.h index ac3013747e..11faa3bdd4 100644 --- a/Libraries/LibC/sys/time.h +++ b/Libraries/LibC/sys/time.h @@ -42,6 +42,7 @@ struct timezone { int tz_dsttime; }; +int adjtime(const struct timeval* delta, struct timeval* old_delta); int gettimeofday(struct timeval* __restrict__, void* __restrict__) __attribute__((nonnull(1))); int settimeofday(struct timeval* __restrict__, void* __restrict__) __attribute__((nonnull(1))); diff --git a/Libraries/LibC/time.cpp b/Libraries/LibC/time.cpp index 8e19609191..73f1a4234d 100644 --- a/Libraries/LibC/time.cpp +++ b/Libraries/LibC/time.cpp @@ -49,6 +49,12 @@ time_t time(time_t* tloc) return tv.tv_sec; } +int adjtime(const struct timeval* delta, struct timeval* old_delta) +{ + int rc = syscall(SC_adjtime, delta, old_delta); + __RETURN_WITH_ERRNO(rc, rc, -1); +} + int gettimeofday(struct timeval* __restrict__ tv, void* __restrict__) { int rc = syscall(SC_gettimeofday, tv);