I have an embedded Linux with a write lock on all system folders (including /etc) and only one folder being freely writable, called /writable
. /etc/localtime
is a symbolic link that points to /writable/localtime
.
My (test) application changes the timezone by copying a different timezone file into the location at /writable/localtime
, then calls tzset()
and tries to read tzname
.
However regardless of whether I copy the timezone information or turn the file into a symlink pointing elsewhere, tzset
does neither change tzname
, nor does it update the offsets (meaning using localtime
yields the same results).
Only after restarting the application, the new timezone takes effect.
Why?
Use a combination of setenv
, unsetenv
and tzset
to force it to do its job.
(Minimal) Example code:
#include <time.h>
int main(int, const char **)
{
//Install new timezone file
setenv("TZ", "UTC", 1);
tzset();
unsetenv("TZ");
tzset();
//Format time to check it worked.
}
This one had me puzzled for a good while, which is why I figured I'd share my findings with the community.
In the sources of glibc, I found that tzset
performs two checks prior to doing its actual work.
The first check is 'looking at the environment variable' (TZ
).
If set, the function compares the value with its (internally) cached value and if the two are equal (strcmp
), tzset aborts there.
But this wasn't the case here, since the environment variable wasn't set at all.
The second check, after making sure that TZ
wasn't set during the last call, compares whether the timezone file (/etc/localtime
) has changed by evaluating its metadata (inode number, modification date, etc.).
And this is where the problem in the above scenario sits: You do not update /etc/localtime
, you update where it points to and tzset
doesn't look that far.
So, by using the above logic, tzset
deduces, that there's nothing to be done.
By setting the environment variable and calling tzset
, you update its internal buffers.tzset
now knows, that you are using a custom timezone string and not the file.
By unsetting the environmental variable in the next step, the first check returns false, because the cached timezone value (UTC
) is different from the current timezone value (NULL
) and the second check also yields false, because the environment variable had been used before
, making it skip the file check altogether.
All of this only applies, if you cannot manipulate /etc/localtime
directly, but need to manipulate the place it points to. Otherwise tzset
would detect 'file has changed' as described above.
Regarding the 'when a new process is started, the correct timezone is used':
When starting a new process, the timezone (which had been changed prior to starting) is evaluated 'anew'.
The above workaround is only required for an already running processes.