adjtimex(2)

๐ŸŒธ adjtimex(2) โ€” System Calls Manual ๐ŸŒธ

okay so bestie, welcome to the manual page for the Linux clock-tuning system calls. This one lives in section 2 of the manual (that's the system calls section, iykyk). Grab a snack, it's a long one. ๐Ÿ’…


โœจ NAME โœจ

adjtimex, clock_adjtime, ntp_adjtime โ€” tune kernel clock ๐Ÿ•ฐ๏ธโœจ


๐Ÿ“š LIBRARY ๐Ÿ“š

Standard C library (libc, -lc)


๐ŸŽ€ SYNOPSIS ๐ŸŽ€

#include <sys/timex.h>

int adjtimex(struct timex *buf);

int clock_adjtime(clockid_t clk_id, struct timex *buf);

int ntp_adjtime(struct timex *buf);

๐Ÿ’– DESCRIPTION ๐Ÿ’–

So Linux runs David L. Mills' clock adjustment algorithm โ€” yes, the legend himself โ€” and if you wanna see the receipts, that's RFC 5905. ๐Ÿ‘‘

The system call adjtimex() reads and optionally sets adjustment parameters for that algorithm. It takes a pointer to a timex structure, updates kernel parameters from (selected) field values, and then hands you back that same structure, all filled in with the current kernel values. It's giving read-then-write-then-read-back. ๐Ÿฆ‹

That structure is declared like this, and every line matters:

struct timex {
    int  modes;      /* Mode selector */
    long offset;     /* Time offset; nanoseconds, if STA_NANO
                        status flag is set, otherwise
                        microseconds */
    long freq;       /* Frequency offset; see NOTES for units */
    long maxerror;   /* Maximum error (microseconds) */
    long esterror;   /* Estimated error (microseconds) */
    int  status;     /* Clock command/status */
    long constant;   /* PLL (phase-locked loop) time constant */
    long precision;  /* Clock precision
                        (microseconds, read-only) */
    long tolerance;  /* Clock frequency tolerance (read-only);
                        see NOTES for units */
    struct timeval time;
                     /* Current time (read-only, except for
                        ADJ_SETOFFSET); upon return, time.tv_usec
                        contains nanoseconds, if STA_NANO status
                        flag is set, otherwise microseconds */
    long tick;       /* Microseconds between clock ticks */
    long ppsfreq;    /* PPS (pulse per second) frequency
                        (read-only); see NOTES for units */
    long jitter;     /* PPS jitter (read-only); nanoseconds, if
                        STA_NANO status flag is set, otherwise
                        microseconds */
    int  shift;      /* PPS interval duration
                        (seconds, read-only) */
    long stabil;     /* PPS stability (read-only);
                        see NOTES for units */
    long jitcnt;     /* PPS count of jitter limit exceeded
                        events (read-only) */
    long calcnt;     /* PPS count of calibration intervals
                        (read-only) */
    long errcnt;     /* PPS count of calibration errors
                        (read-only) */
    long stbcnt;     /* PPS count of stability limit exceeded
                        events (read-only) */
    int tai;         /* TAI offset, as set by previous ADJ_TAI
                        operation (seconds, read-only,
                        since Linux 2.6.26) */
    /* Further padding bytes to allow for future expansion */
};

Note the padding bytes at the end โ€” the kernel is future-proofing, main character energy fr fr. ๐Ÿ’œ


๐ŸŒˆ The modes field ๐ŸŒˆ

The modes field is the one that decides which parameters, if any, get set. (And as we'll spill later on this page, the constants used for ntp_adjtime() are equivalent, they just got different names.) It's a bit mask โ€” so you take a bitwise OR of zero or more of these bits:

  • ADJ_OFFSET โ€” sets the time offset from buf.offset. Since Linux 2.6.26, whatever value you hand over gets clamped to the range (-0.5s, +0.5s). On older kernels? Out-of-range value = EINVAL error. ๐Ÿ’€
  • ADJ_FREQUENCY โ€” sets the frequency offset from buf.freq. Since Linux 2.6.26, the value is clamped to the range (-32768000, +32768000). On older kernels, an out-of-range value gets you an EINVAL error.
  • ADJ_MAXERROR โ€” sets the maximum time error from buf.maxerror.
  • ADJ_ESTERROR โ€” sets the estimated time error from buf.esterror.
  • ADJ_STATUS โ€” sets the clock status bits from buf.status. (Full breakdown of those bits is down below, don't worry. ๐Ÿซถ)
  • ADJ_TIMECONST โ€” sets the PLL time constant from buf.constant. And heads up: if the STA_NANO status flag (see below) is clear, the kernel adds 4 to this value. Little sneaky bonus number, no cap.
  • ADJ_SETOFFSET (since Linux 2.6.39) โ€” adds buf.time to the current time. If buf.status includes the ADJ_NANO flag, then buf.time.tv_usec gets interpreted as a nanosecond value; otherwise it's read as microseconds.

The value of buf.time is the sum of its two fields, but here's the catch: buf.time.tv_usec must always be nonnegative. Here's how you normalize a timeval with nanosecond resolution:

while (buf.time.tv_usec < 0) {
    buf.time.tv_sec  -= 1;
    buf.time.tv_usec += 1000000000;
}
  • ADJ_MICRO (since Linux 2.6.26) โ€” selects microsecond resolution.
  • ADJ_NANO (since Linux 2.6.26) โ€” selects nanosecond resolution. Only one of ADJ_MICRO and ADJ_NANO should be specified, bestie. Pick a lane. ๐Ÿ’…
  • ADJ_TAI (since Linux 2.6.26) โ€” sets the TAI (Atomic International Time) offset from buf.constant.

Do not use ADJ_TAI together with ADJ_TIMECONST, because ADJ_TIMECONST also uses the buf.constant field. They'd be fighting over the same variable and it would be a whole mess. ๐Ÿ˜ญ

For the complete explanation of TAI and how TAI differs from UTC, see BIPM.

  • ADJ_TICK โ€” sets the tick value from buf.tick.

๐Ÿฆ‹ The multibit mask values ๐Ÿฆ‹

Alternatively โ€” and this is the plot twist โ€” modes can be set to either of the following (these are multibit mask values), and if you do that, you should not specify any other bits in modes:

  • ADJ_OFFSET_SINGLESHOT โ€” the old-fashioned adjtime(3): (gradually) adjusts the time by the value in buf.offset, which specifies an adjustment in microseconds.
  • ADJ_OFFSET_SS_READ (functional since Linux 2.6.28) โ€” returns (in buf.offset) the remaining amount of time still to be adjusted after an earlier ADJ_OFFSET_SINGLESHOT operation. This feature got added in Linux 2.6.24, but it was lowkey broken until Linux 2.6.28. So if you're on one of those middle versions, temperamental behavior, not your fault. ๐Ÿ™ˆ
๐Ÿšจ Important: ordinary users are restricted to a value of either 0 or ADJ_OFFSET_SS_READ for modes. Only the superuser may set any parameters. Sorry bestie, root privileges required. ๐Ÿ‘‘

๐Ÿ’œ The buf.status field ๐Ÿ’œ

buf.status is a bit mask used to set and/or retrieve status bits tied to the NTP implementation. Some bits in the mask are both readable and settable, and some are strictly read-only. Let's go through all of them:

  • STA_PLL (read-write) โ€” enables phase-locked loop (PLL) updates via ADJ_OFFSET.
  • STA_PPSFREQ (read-write) โ€” enables PPS (pulse-per-second) frequency discipline.
  • STA_PPSTIME (read-write) โ€” enables PPS time discipline.
  • STA_FLL (read-write) โ€” selects frequency-locked loop (FLL) mode.
  • STA_INS (read-write) โ€” inserts a leap second after the last second of the UTC day, which extends the last minute of the day by one second. And it's persistent: leap-second insertion happens each day, so long as this flag stays set. ๐Ÿ“…โœจ
  • STA_DEL (read-write) โ€” deletes a leap second at the last second of the UTC day. Same deal: leap-second deletion happens each day, so long as this flag remains set.
  • STA_UNSYNC (read-write) โ€” clock unsynchronized.
  • STA_FREQHOLD (read-write) โ€” holds the frequency. Here's the vibe: normally, adjustments made via ADJ_OFFSET also cause dampened frequency adjustments to happen as a side effect. So a single call fixes the current offset, but as you keep making offsets in the same direction over and over, those small frequency adjustments accumulate and fix the long-term skew. This flag stops that small frequency adjustment from being made when correcting for an ADJ_OFFSET value. It's the "no, stay exactly as you are" button. ๐Ÿ’…
  • STA_PPSSIGNAL (read-only) โ€” a valid PPS (pulse-per-second) signal is present.
  • STA_PPSJITTER (read-only) โ€” PPS signal jitter exceeded.
  • STA_PPSWANDER (read-only) โ€” PPS signal wander exceeded.
  • STA_PPSERROR (read-only) โ€” PPS signal calibration error.
  • STA_CLOCKERR (read-only) โ€” clock hardware fault. ๐Ÿ’€
  • STA_NANO (read-only; since Linux 2.6.26) โ€” resolution (0 = microsecond, 1 = nanoseconds). Set via ADJ_NANO, cleared via ADJ_MICRO.
  • STA_MODE (since Linux 2.6.26) โ€” mode (0 = Phase Locked Loop, 1 = Frequency Locked Loop).
  • STA_CLK (read-only; since Linux 2.6.26) โ€” clock source (0 = A, 1 = B); currently unused.
โš ๏ธ Attempts to set read-only status bits are silently ignored. No error, no drama, it just doesn't happen. Not me finding that out the hard way... ๐Ÿ™ˆ

๐ŸŒธ clock_adjtime() ๐ŸŒธ

The clock_adjtime() system call (added in Linux 2.6.39) behaves just like adjtimex(), except it takes an extra clk_id argument to specify the particular clock you want it to act on. Choose your fighter. โฐ


๐Ÿงš ntp_adjtime() ๐Ÿงš

The ntp_adjtime() library function (described in the NTP "Kernel Application Program API", a.k.a. KAPI) is the more portable interface for doing the exact same task as adjtimex(). Other than the following points, it is identical to adjtimex():

  • The constants used in modes are prefixed with "MOD_" rather than "ADJ_", and they have the same suffixes (thus, MOD_OFFSET, MOD_FREQUENCY, and so on) โ€” other than the exceptions noted in the following points.
  • MOD_CLKA is the synonym for ADJ_OFFSET_SINGLESHOT.
  • MOD_CLKB is the synonym for ADJ_TICK.
  • The is no synonym for ADJ_OFFSET_SS_READ, which is not described in the KAPI.

okay that's it for part 1 bestie โ€” part 2 is coming right up, don't go anywhere ๐Ÿ’–

๐Ÿ’– RETURN VALUE ๐Ÿ’–

okay so bestie, on success adjtimex() and ntp_adjtime() hand you back the clock state, which is one of these iconic little values:

  • TIME_OK โ€” clock is synchronized, nothing leap-second-y is pending. we're chill. ๐Ÿ˜Œ
  • TIME_INS โ€” heads up, a leap second is gonna get inserted at the end of the UTC day.
  • TIME_DEL โ€” a leap second is gonna get deleted at the end of the UTC day. bye bestie ๐Ÿ’€
  • TIME_OOP โ€” the leap second insertion is literally in progress right now, mid-oop.
  • TIME_WAIT โ€” the leap-second insertion or deletion just finished. this value keeps coming back until the next ADJ_STATUS operation clears the STA_INS and STA_DEL flags.
  • TIME_ERROR โ€” the system clock is not synchronized to a reliable server. you get this whenever any of the following is true, no cap:
    • either STA_UNSYNC or STA_CLOCKERR is set.
    • STA_PPSSIGNAL is clear and either STA_PPSFREQ or STA_PPSTIME is set.
    • STA_PPSTIME and STA_PPSJITTER are both set.
    • STA_PPSFREQ is set and either STA_PPSWANDER or STA_PPSJITTER is set.

The symbolic name TIME_BAD is a synonym for TIME_ERROR, kept around for backward compatibility. legacy queen, we respect her ๐Ÿ™

heads up bestie: starting with Linux 3.4, the call operates asynchronously, so the return value usually will not reflect a state change caused by the call itself. it's giving "don't @ me, check back later" โœจ

On failure, these calls return -1 and set errno to show you what went wrong.


๐Ÿ˜ญ ERRORS ๐Ÿ˜ญ

  • EFAULT โ€” buf does not point to writable memory. she cannot write there, bestie, that memory said no.
  • EINVAL โ€” (before Linux 2.6.26) an attempt was made to set buf.freq to a value outside the range (-33554432, +33554432).
  • EINVAL โ€” (before Linux 2.6.26) an attempt was made to set buf.offset to a value outside the permitted range. Before Linux 2.0, the permitted range was (-131072, +131072). From Linux 2.0 onwards, the permitted range was (-512000, +512000).
  • EINVAL โ€” an attempt was made to set buf.status to a value other than those listed above. (you tried to make up your own status flag? not on this kernel's watch ๐Ÿ’…)
  • EINVAL โ€” the clk_id given to clock_adjtime() is invalid, for one of two reasons. Either the System-V style hard-coded positive clock ID value is out of range, or the dynamic clk_id does not refer to a valid instance of a clock object. See clock_gettime(2) for a discussion of dynamic clocks.
  • EINVAL โ€” an attempt was made to set buf.tick to a value outside the range 900000/HZ to 1100000/HZ, where HZ is the system timer interrupt frequency.
  • ENODEV โ€” the hot-pluggable device (like USB for example) represented by a dynamic clk_id has disappeared after its character device was opened. so rude. See clock_gettime(2) for a discussion of dynamic clocks.
  • EOPNOTSUPP โ€” the given clk_id does not support adjustment. it's just not that kind of clock, bestie.
  • EPERM โ€” buf.modes is neither 0 nor ADJ_OFFSET_SS_READ, and the caller does not have sufficient privilege. Under Linux, the CAP_SYS_TIME capability is required. ๐Ÿ”’

๐ŸŒˆ ATTRIBUTES ๐ŸŒˆ

for an explanation of the terms used in this section, see attributes(7).

  • Interface: ntp_adjtime() โ€” Attribute: Thread safety โ€” Value: MT-Safe

(she is MT-Safe, she is unbothered, she is thriving ๐Ÿซถ)


๐Ÿ‘‘ STANDARDS ๐Ÿ‘‘

  • adjtimex()
  • clock_adjtime() โ€” both are Linux.

The preferred API for the NTP daemon is ntp_adjtime(). periodt.


๐Ÿงš NOTES ๐Ÿงš

In struct timex, freq, ppsfreq, and stabil are ppm (parts per million) with a 16-bit fractional part, which means that a value of 1 in one of those fields actually means 2^-16 ppm, and 2^16=65536 is 1 ppm. This is the case for both input values (in the case of freq) and output values. so tiny. so precise. math girlie behavior โœจ

The leap-second processing triggered by STA_INS and STA_DEL is done by the kernel in timer context. Thus, it will take one tick into the second for the leap second to be inserted or deleted. lowkey a whole tick of patience required ๐Ÿ•ฐ๏ธ


๐Ÿฆ‹ SEE ALSO ๐Ÿฆ‹

clock_gettime(2), clock_settime(2), settimeofday(2), adjtime(3), ntp_gettime(3), capabilities(7), time(7), adjtimex(8), hwclock(8)

NTP "Kernel Application Program Interface"


and that's the whole vibe bestie, we ate that entire page and left no crumbs ๐Ÿ“ this one's from Linux man-pages 6.20, dated 2026-02-08 โ€” go off, timekeepers, you absolute main characters ๐Ÿ’œ

๐Ÿ“ฆ where this page comes from
  • arch rolling: man-pages 6.20-1 ยท BSD-2-Clause AND BSD-3-Clause AND BSD-4.3TAHOE AND BSD-4-Clause-UC AND GPL-1.0-or-later AND GPL-2.0-only AND GPL-2.0-or-later AND GPL-3.0-or-later AND LGPL-3.0-linking-exception AND LGPL-3.0-or-later AND LicenseRef-Public-Domain AND LicenseRef-UltraPermissive AND Linux-man-pages-1-para AND Linux-man-pages-copyleft AND Linux-man-pages-copyleft-2-para AND Linux-man-pages-copyleft-var AND MIT AND Spencer-94
  • nixpkgs unstable: man-pages 6.20
  • ๐Ÿ“œ the page's license: GPL-2.0-or-later
  • ๐Ÿ’– girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms