clinuxembedded-linux

Why does my timezone not update in embedded linux?


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?


Solution

  • TL;DR

    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.
    }
    

    What's going on?

    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.