NetBSD/sys/msdosfs/msdosfs_conv.c

361 lines
8.3 KiB
C

/*
* 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
*
* $Id: msdosfs_conv.c,v 1.3 1994/03/03 00:51:33 paulus Exp $
*/
/*
* System include files.
*/
#include <sys/param.h>
#include <sys/time.h>
#include <sys/kernel.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(tvp, ddp, dtp)
struct timeval *tvp;
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 = tvp->tv_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, tvp)
u_short dd;
u_short dt;
struct timeval *tvp;
{
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)
+ ((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 (%d)\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;
}
tvp->tv_sec = seconds + lastseconds + (tz.tz_minuteswest * 60)
/* -+ daylight savings time correction */ ;
tvp->tv_usec = 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] == '.' && un[1] == '\0') {
dn[0] = '.';
return;
}
if (un[0] == '.' && un[1] == '.' && un[2] == '\0') {
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) && c != '.') {
un++;
unlen--;
}
/*
* If we stopped on a '.', then get past it.
*/
if (c == '.')
un++;
/*
* 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