Linux and the Y2K Bug
Robert Kiesling
Linux, like other POSIX-compliant operating systems, provides facilities for coping with the Y2K bug, and software written properly for Linux should be able to cope with the end of the millennium. As a systems administrator, however, you may want to test both your Linux systems and any applications running on them for date compliance.
This article describes the time keeping functions that are available in the Linux C libraries, Version 5. The GNU glib2 libraries, which are still in the early stages of development, will provide similar functionality and compliance with time keeping standards. I also explore how you might go about testing Linux-based applications for date compliance.
Linux Time
Linux keeps track of the time using the 32-bit time_t type that maintains the count of UTC calendar time, which is the number of seconds since January 1, 1970. It is declared in time.h as the following:
typedef long time_t;
The standard UNIX utilities (or the free software versions distributed by the GNU project) that have been ported to Linux, support this data type. Assuming that the hardware clock handles the dates in the next century correctly, time keeping should be correct until the year 2038, when the 32-bit time_t counter will overflow. Solving what may become known as the Y2038K bug is beyond the scope of this article, however.
The Linux libraries include the following functions to provide time and date information.
time() /* Returns the value of time_t in seconds. */
ctime() /* Returns ASCII formatted output from time_t. */
stime() /* Set the date and time using UTC calendar time. */
Functions related to ctime(), such as asctime(), gmtime(), localtime(), and mktime(), use the value stored in time_t and return hours, minutes, seconds, day, date, month, and year in either data structure tm, or in ASCII format.
The tm struct is declared in time.h as:
struct tm {
int tm_sec;
int tm_min;
int tm_hour;
int tm_mday;
int tm_mon;
int tm_year;
int tm_wday;
int tm_yday;
int tm_isdst;
/* Those are for future use. */
long int __tm_gmtoff__;
__const char *__tm_zone__;
};
strftime() provides formatted output using a printf-like format specifier, and uses the system's local data for time zone information. For example, the date program uses the strftime() function to output the date in RFC international time format.
Sat Aug 29 10:34:43 EDT 1998
If the system's hardware clock can handle dates in the next century correctly, date will display the date with the correct day, date, and year.
Sun Aug 29 10:53:07 EDT 1999
Tue Aug 29 10:46:41 EDT 2000
Wed Aug 29 10:49:29 EDT 2001
ANSI-standard time functions do not need to provide the day of the week to be specified, although POSIX-compliant functions do.
Testing Software
On Intel-architecture machines, the system hardware clock is set from a CMOS, battery-maintained timer when the system is powered on. On machines that use the Intel 80286 and later processors, the CMOS time is set using a hardware-embedded, menu-driven program that is accessible before the system software boots.
Booting a multi-user system for testing purposes, however, is impractical, not in the least because resetting the time can disrupt the operation of users' programs. Because Linux uses the memory management facilities of 80386 and later processors, programs run from user space to set the hardware clock can cause a signal 11 segmentation violation. One solution is to use one of the many utilities provided for MS-DOS that sets the hardware clock using direct BIOS calls. These utilities can be run under the MS-DOS emulator that is provided for Linux.
This solution, however, does not address the problem of the date being reset for other users. Ideally, we would like to measure the performance of the software being tested without disrupting other users' programs. This means we would like to substitute our own counter for the system's time_t counter. The program getutctime, shown in Listing 1, takes its argument in the format hours:minutes:seconds:month:day:year and returns the calendar time using the mktime() function. If no argument is given, the program returns the current calendar time.
> getutctime
904427231.
> getutctime 12:30:00:4:16:2002
1018978200.
The program doesn't do any range checking of the date and time values. The values are stored in the tm structure as integer representations of the command line argument, with the exception that months are counted from 0, and years are counted from 1900. However, the time_t counter increments correctly after the year 2000.
Inserting a User-Specified Time into a Routine
With the information given previously, it is possible to substitute the calendar value of a given date and time for the actual system time when testing software. For example, the GNU date utility takes the time either from the system clock or from the command line. It converts the UTC calendar time to a tm struct using local time information via the localtime() function, which is then formatted using the strftime() function into the time and date displayed by the output.
Normally, the system time is taken from the system clock, but it is easy enough to substitute a test value. A code fragment that formats a user-supplied time and date in a similar manner might look like this:
#include <time.h>
test_time{ time_t user_supplied_time )
{
struct tm *timestruct;
char buf[80];
char fmt[] = "%a %b %e %H:%M:%S %Z %Y";
timestruct = localtime( &user_supplied_time );
strftime(&buf, sizeof(buf), fmt, tm);
printf("%s\n", buf);
}
Checking Y2K Compliance in Perl and awk Programs
Resetting the time in Perl can be done with the localtime() function, which is similar to the C function. localtime() returns a formatted array of elements with a format similar to those of the C-language tm struct. The short program shown here returns either the current system time, if no argument is passed to it, or the formatted user-specified time if one is provided as an argument.
#!/usr/bin/perl
if ( $_ = $ARGV[0], /^-/ )
{
print scalar localtime($ARGV[0]), "\n";
}
else
{
print scalar localtime, "\n";
}
The same program written in awk, using the built-in systime() and strftime() functions, looks like this:
#! /usr/bin/awk -f
BEGIN {
if ( ARGC == 1 ) {
print strftime("%a %b %e %H:%M:%S %Z %Y ", systime());
}
else {
print strftime("%a %b %e %H:%M:%S %Z %Y ", ARGV[1]);
}
}
Testing Software by Setting the System Time
Sometimes, of course, resetting the system time is much easier. If you are testing a Perl program for end of the century compliance, setting the system clock externally is much simpler than, for example, digging through the internals of nonstandard time and date routines if you do not have access to the program source code.
The C stime() function resets the system clock, but you must have superuser privileges on the system to use it. The simple program shown in setutctime, resets the time using a UTC calendar time in long integer format, like those provided by getutctime. setutctime does not perform any checking of user input, but does return an error if the person executing the program does not have permission to reset the system time.
Conclusion
The article shows how to locate Y2K millenium bugs in programs that do not follow the timekeeping standards of Linux or other POSIX-compliant operating systems. The code can provide the building blocks for more complex test suites written in C, Perl, and awk. The specific actions to correct non-Y2K conforming programs will vary depending on your site's needs and the availability of Y2K conformant replacement software. If the source code of a non-conforming program is available, this article provides a few techniques to re-code the software to POSIX standards so dates will be correct after the year 2000.
About the Author
Robert Kiesling is the editor of Linux: The Complete Reference, 6th Edition and a contributor to Linux Installation and Getting Started. He is also the maintainer of the Linux Frequently Asked Questions (FAQ) list. Comments should be directed to kiesling@terracom.net.
|