freebsd-src/sys/msdosfs/msdosfs_conv.c
Bruce Evans 63e4f22a2a Fix numerous timestamp bugs.
DE_UPDATE was confused with DE_MODIFIED in some places (they do have
confusing names).  Handle them exactly the same as IN_UPDATE and
IN_MODIFIED.  This fixes chmod() and chown() clobbering the mtime
and other bugs.

DE_MODIFIED was set but not used.

Parenthesize macro args.

DE_TIMES() now takes a timeval arg instead of a timespec arg.  It was
stupid to use a macro for speed and do unused conversions to prepare
for the macro.

Restore the left shifting of the DOS seconds count by 1.  It got
lost among the shifts for the bitfields, so DOS seconds counts
appeared to range from 0 to 29 seconds (step 1) instead of 0 to 58
seconds (step 2).

Actually use the passed-in mtime in deupdat() as documented so that
utimes() works.

Change `extern __inline's to `static inline's so that msdosfs_fat.o
can be linked when it is compiled without -O.

Remove faking of directory mtimes to always be the current time.  It's
more surprising for directory mtimes to change when you read the
directories than for them not to change when you write the directories.
This should be controlled by a mount-time option if at all.
1994-12-12 12:35:50 +00:00

361 lines
8.4 KiB
C

/* $Id: msdosfs_conv.c,v 1.2 1994/09/27 20:42:42 phk Exp $ */
/* $NetBSD: msdosfs_conv.c,v 1.6.2.1 1994/08/30 02:27:57 cgd Exp $ */
/*
* Written by Paul Popelka (paulp@uts.amdahl.com)
*
* You can do anything you want with this software, just don't say you wrote
* it, and don't remove this notice.
*
* This software is provided "as is".
*
* The author supplies this software to be publicly redistributed on the
* understanding that the author is not responsible for the correct
* functioning of this software in any circumstances and is not liable for
* any damages caused by this software.
*
* October 1992
*/
/*
* System include files.
*/
#include <sys/param.h>
#include <sys/time.h>
#include <sys/kernel.h> /* defines tz */
#include <sys/systm.h> /* defines tz */
/*
* MSDOSFS include files.
*/
#include <msdosfs/direntry.h>
/*
* Days in each month in a regular year.
*/
u_short regyear[] = {
31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31
};
/*
* Days in each month in a leap year.
*/
u_short leapyear[] = {
31, 29, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31
};
/*
* Variables used to remember parts of the last time conversion. Maybe we
* can avoid a full conversion.
*/
u_long lasttime;
u_long lastday;
u_short lastddate;
u_short lastdtime;
/*
* Convert the unix version of time to dos's idea of time to be used in
* file timestamps. The passed in unix time is assumed to be in GMT.
*/
void
unix2dostime(tsp, ddp, dtp)
struct timespec *tsp;
u_short *ddp;
u_short *dtp;
{
u_long t;
u_long days;
u_long inc;
u_long year;
u_long month;
u_short *months;
/*
* If the time from the last conversion is the same as now, then
* skip the computations and use the saved result.
*/
t = tsp->ts_sec - (tz.tz_minuteswest * 60)
/* +- daylight savings time correction */ ;
if (lasttime != t) {
lasttime = t;
lastdtime = (((t % 60) >> 1) << DT_2SECONDS_SHIFT)
+ (((t / 60) % 60) << DT_MINUTES_SHIFT)
+ (((t / 3600) % 24) << DT_HOURS_SHIFT);
/*
* If the number of days since 1970 is the same as the last
* time we did the computation then skip all this leap year
* and month stuff.
*/
days = t / (24 * 60 * 60);
if (days != lastday) {
lastday = days;
for (year = 1970;; year++) {
inc = year & 0x03 ? 365 : 366;
if (days < inc)
break;
days -= inc;
}
months = year & 0x03 ? regyear : leapyear;
for (month = 0; month < 12; month++) {
if (days < months[month])
break;
days -= months[month];
}
lastddate = ((days + 1) << DD_DAY_SHIFT)
+ ((month + 1) << DD_MONTH_SHIFT);
/*
* Remember dos's idea of time is relative to 1980.
* unix's is relative to 1970. If somehow we get a
* time before 1980 then don't give totally crazy
* results.
*/
if (year > 1980)
lastddate += (year - 1980) << DD_YEAR_SHIFT;
}
}
*dtp = lastdtime;
*ddp = lastddate;
}
/*
* The number of seconds between Jan 1, 1970 and Jan 1, 1980. In that
* interval there were 8 regular years and 2 leap years.
*/
#define SECONDSTO1980 (((8 * 365) + (2 * 366)) * (24 * 60 * 60))
u_short lastdosdate;
u_long lastseconds;
/*
* Convert from dos' idea of time to unix'. This will probably only be
* called from the stat(), and fstat() system calls and so probably need
* not be too efficient.
*/
void
dos2unixtime(dd, dt, tsp)
u_short dd;
u_short dt;
struct timespec *tsp;
{
u_long seconds;
u_long m, month;
u_long y, year;
u_long days;
u_short *months;
seconds = (((dt & DT_2SECONDS_MASK) >> DT_2SECONDS_SHIFT) << 1)
+ ((dt & DT_MINUTES_MASK) >> DT_MINUTES_SHIFT) * 60
+ ((dt & DT_HOURS_MASK) >> DT_HOURS_SHIFT) * 3600;
/*
* If the year, month, and day from the last conversion are the
* same then use the saved value.
*/
if (lastdosdate != dd) {
lastdosdate = dd;
days = 0;
year = (dd & DD_YEAR_MASK) >> DD_YEAR_SHIFT;
for (y = 0; y < year; y++) {
days += y & 0x03 ? 365 : 366;
}
months = year & 0x03 ? regyear : leapyear;
/*
* Prevent going from 0 to 0xffffffff in the following
* loop.
*/
month = (dd & DD_MONTH_MASK) >> DD_MONTH_SHIFT;
if (month == 0) {
printf(
"dos2unixtime(): month value out of range (%ld)\n",
month);
month = 1;
}
for (m = 0; m < month - 1; m++) {
days += months[m];
}
days += ((dd & DD_DAY_MASK) >> DD_DAY_SHIFT) - 1;
lastseconds = (days * 24 * 60 * 60) + SECONDSTO1980;
}
tsp->ts_sec = seconds + lastseconds + (tz.tz_minuteswest * 60)
/* -+ daylight savings time correction */ ;
tsp->ts_nsec = 0;
}
/*
* Cheezy macros to do case detection and conversion for the ascii
* character set. DOESN'T work for ebcdic.
*/
#define isupper(c) (c >= 'A' && c <= 'Z')
#define islower(c) (c >= 'a' && c <= 'z')
#define toupper(c) (c & ~' ')
#define tolower(c) (c | ' ')
/*
* DOS filenames are made of 2 parts, the name part and the extension part.
* The name part is 8 characters long and the extension part is 3
* characters long. They may contain trailing blanks if the name or
* extension are not long enough to fill their respective fields.
*/
/*
* Convert a DOS filename to a unix filename. And, return the number of
* characters in the resulting unix filename excluding the terminating
* null.
*/
int
dos2unixfn(dn, un)
u_char dn[11];
u_char *un;
{
int i;
int ni;
int ei;
int thislong = 0;
u_char c;
u_char *origun = un;
/*
* Find the last character in the name portion of the dos filename.
*/
for (ni = 7; ni >= 0; ni--)
if (dn[ni] != ' ')
break;
/*
* Find the last character in the extension portion of the
* filename.
*/
for (ei = 10; ei >= 8; ei--)
if (dn[ei] != ' ')
break;
/*
* Copy the name portion into the unix filename string. NOTE: DOS
* filenames are usually kept in upper case. To make it more unixy
* we convert all DOS filenames to lower case. Some may like this,
* some may not.
*/
for (i = 0; i <= ni; i++) {
c = dn[i];
*un++ = isupper(c) ? tolower(c) : c;
thislong++;
}
/*
* Now, if there is an extension then put in a period and copy in
* the extension.
*/
if (ei >= 8) {
*un++ = '.';
thislong++;
for (i = 8; i <= ei; i++) {
c = dn[i];
*un++ = isupper(c) ? tolower(c) : c;
thislong++;
}
}
*un++ = 0;
/*
* If first char of the filename is SLOT_E5 (0x05), then the real
* first char of the filename should be 0xe5. But, they couldn't
* just have a 0xe5 mean 0xe5 because that is used to mean a freed
* directory slot. Another dos quirk.
*/
if (*origun == SLOT_E5)
*origun = 0xe5;
return thislong;
}
/*
* Convert a unix filename to a DOS filename. This function does not ensure
* that valid characters for a dos filename are supplied.
*/
void
unix2dosfn(un, dn, unlen)
u_char *un;
u_char dn[11];
int unlen;
{
int i;
u_char c;
/*
* Fill the dos filename string with blanks. These are DOS's pad
* characters.
*/
for (i = 0; i <= 10; i++)
dn[i] = ' ';
/*
* The filenames "." and ".." are handled specially, since they
* don't follow dos filename rules.
*/
if (un[0] == '.' && unlen == 1) {
dn[0] = '.';
return;
}
if (un[0] == '.' && un[1] == '.' && unlen == 2) {
dn[0] = '.';
dn[1] = '.';
return;
}
/*
* Copy the unix filename into the dos filename string upto the end
* of string, a '.', or 8 characters. Whichever happens first stops
* us. This forms the name portion of the dos filename. Fold to
* upper case.
*/
for (i = 0; i <= 7 && unlen && (c = *un) && c != '.'; i++) {
dn[i] = islower(c) ? toupper(c) : c;
un++;
unlen--;
}
/*
* If the first char of the filename is 0xe5, then translate it to
* 0x05. This is because 0xe5 is the marker for a deleted
* directory slot. I guess this means you can't have filenames
* that start with 0x05. I suppose we should check for this and
* doing something about it.
*/
if (dn[0] == SLOT_DELETED)
dn[0] = SLOT_E5;
/*
* Strip any further characters up to a '.' or the end of the
* string.
*/
while (unlen && (c = *un)) {
un++;
unlen--;
/* Make sure we've skipped over the dot before stopping. */
if (c == '.')
break;
}
/*
* Copy in the extension part of the name, if any. Force to upper
* case. Note that the extension is allowed to contain '.'s.
* Filenames in this form are probably inaccessable under dos.
*/
for (i = 8; i <= 10 && unlen && (c = *un); i++) {
dn[i] = islower(c) ? toupper(c) : c;
un++;
unlen--;
}
}
/*
* Get rid of these macros before someone discovers we are using such
* hideous things.
*/
#undef isupper
#undef islower
#undef toupper
#undef tolower