Case: 2005/361 PCFS timestamp handling cleanup Author: Frank Hofmann Summary: This proposal requests, for Patch binding, * to simplify PCFS timestamp handling by removing the broken code in it that attempts to handle daylight saving time adjustments. * to modify PCFS timestamp handling to exhibit a defined behaviour at the attempt to set/query a timestamp that is outside the range of time PCFS timestamps can cover, or outside the range of time 32bit UNIX time_t can cover. The Problem Timestamps on FAT filesystems cover the range between 01/Jan/1980, 00:00:00, and 31/12/2127, 23:59:59, and are represented as date/time bitfields, recorded in local time (of the recording timezone). In addition to that, FAT knows multiple types of timestamps but apart from the "last written" time all fields are optional. This causes two problems: First, this results in a problem "how to represent local time". The fact that FAT specifies time to be recorded in local time but fails to specify a way for the recording implementation to store information about the recording timezone on the media makes it impossible for receiving/reading systems to accurately report FAT timestamps. The best possible way of dealing with this is to provide a way for the "user" (the person initiating access to a FAT medium on the receiving system) to tell the filesystem what time correction to apply. PCFS allows this via argument to the mount(2) system call, "struct pcfs_args", which contains two fields, "timezone" (which specifies seconds west of UT) and "daylight" (which specifies whether DST adjustments shall be made). The use of the "daylight" parameter is: a) redundant (a corresponding offset could be added to the "timezone" parameter to get the same effect") b) inconsistent (does it apply to the creation of new files, i.e. PCFS being the recording implementation, or does it apply to the access to existing files, i.e. PCFS being the receiving implementation) c) incomplete (there is no shared timezone database between all recording/receiving implementations of FAT, and DST rules have changed over the lifetime of FAT and continue to change even today). Second, "time_t and FAT don't match". There's a disparity between UNIX time_t (32bit as well as 64bit) and FAT time/date - they cannot be unambiguously mapped onto each other for the whole range of timestamps that either type (32bit time_t, 64bit time_t, FAT timestamp) can cover. With the current PCFS code, this causes several problems: a) If we're running a 64bit kernel, we dutifully return a 64bit time_t post-2038 (UNIX epoch) if the FAT timestamp specifies such. This causes stat() syscalls done by 32bit applications to fail with EOVERFLOW. It breaks all our file utils, including touch(1), so that such a file cannot even be "fixed" by touch'ing it. b) If we're running a 32bit kernel, the calculation of the UNIX time corresponding to a given FAT timestamp overflows and hence for post-UNIX-epoch FAT times, essentially a random value is returned. This causes no failure but prevents applications from detecting that something is odd about a file's access/mod time. c) the optional timestamp fields may, in case they contain uninitialized instead of valid data, cause unexpected failure on access when running a 64bit kernel. The Proposed Solution * DST handling The code for dealing with daylight savings time adjustments in the conversion between UNIX time_t and FAT timestamps will be removed. Local time adjustment will be done exclusively based on the "seconds west of UT" information. Given the abovementioned restriction (no way of discovering the timezone in which the medium was created, and all such attempts therefore necessarily being "best guess"), the timezone correction via "seconds west of UT" is more than sufficient. * FAT/UNIX time disparity handling For dealing with the disparity of UNIX time and FAT time, a pair of PCFS mount options "clamptime" / "noclamptime" will be introduced. The behaviour will be as follows: Definitions used below ====================== "FAT 0" is the earliest date/time representable in timestamps on a FAT filesystem - 01/01/1980, 00:00:00. "FAT end" is the last date/time representable in timestamps on a FAT filesystem - 31/12/2127 23:59:59. "N/A" means that the implementation cannot return or the user cannot supply a value in this range due to limitations in the internal representation (data types used). "UNIX 0" is the time represented by (time_t)0. "UNIX32 end" is the time represented by (time_t)INT32_MAX. "(unambiguous)" means that a 1:1 mapping between FAT time and UNIX time_t exists, keeping sizeof(time_t) in mind. time therefore splits into four regions: v UNIX 0 v FAT 0 v UNIX32 end v FAT end ... R1 | R2 | R3 | R4 ... R1 and R4 are unbounded on lower/upper end. Behaviour ========= The following table will apply if the "clamptime" mount option is in effect: 32bit set time 32bit get time 64bit set time 64bit get time ----------------------------------------------------------------------- R1 EOVERFLOW N/A EOVERFLOW N/A R2 (unambiguous) (unambiguous) (unambiguous) (unambiguous) R3 N/A UNIX32 end EOVERFLOW UNIX32 end R4 N/A N/A EOVERFLOW N/A The following table will apply if the "noclamptime" mount option is in effect: 32bit set time 32bit get time 64bit set time 64bit get time ----------------------------------------------------------------------- R1 EOVERFLOW N/A EOVERFLOW N/A R2 (unambiguous) (unambiguous) (unambiguous) (unambiguous) R3 N/A EOVERFLOW (unambiguous) (unambiguous) R4 N/A N/A EOVERFLOW N/A The difference clamptime/noclamptime will be only in R3. For "clamptime", 32bit and 64bit applications will behave identically on system calls setting/querying timestamps on a PCFS filesystem, the range of timestamps that PCFS will allow to set/query will be limited to [ FAT 0 ... UNIX32 end ]. For "noclamptime", 32bit applications will fail with EOVERFLOW errors when attempting to query a timestamp whose FAT representation is after "UNIX32 end", while 64bit applications will have access to the full range [ FAT 0 ... FAT end ] of timestamps that FAT supports. The attempt to set a timestamp on PCFS to a value outside of the range that PCFS supports (R1 and R4 above) will return EOVERFLOW to the application in any case, regardless of whether the app is 32bit (possible only for R1) or 64bit (possible for R1 and R4). Default behaviour and reasoning =============================== Default behaviour at time of implementation will be "clamptime". The reason to do this is to retain the current user experience of "32bit apps on 32bit kernels work" (never see EOVERFLOW on stat). From a pure standards point of view, this positive user experience is only due to a bug (we're not returning EOVERFLOW on a 32bit kernel ever because timestamp numerics in PCFS are broken), but there are good reasons to make this the default behaviour nonetheless: a) functionality regressions (something that is perceived to work breaks by this change) are undesirable. It does not matter that "it worked only due to a bug", user perception "it worked" must be preserved. b) Solaris, as of this writing, does not supply 64bit file utilities throughout. I.e. even fixing a "broken" timestamp (by touching the file) is not possible. 64bit file utilities would not solve this problem, we still support 32bit kernels on x86 platforms. The time to revisit the decision on which of (no)clamptime shall be the default behaviour will need to be revisited once we EOF the 32bit kernel, or provide a full set of 64bit file utilities, whatever earlier. MAN PAGE CHANGES The man page for pcfs(7fs) will be changed to reflect that the "daylight" member of "struct pcfs" has no effect. The man page for mount_pcfs(1M) will be changed to describe the new mount options "clamptime" and "noclamptime" including the choice of "clamptime" as default.