NetBSD/sys/pcfs/pcfs_conv.c

368 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: pcfs_conv.c,v 1.2 1993/05/20 03:34:09 cgd Exp $
*/
/*
* System include files.
*/
#include "param.h"
#include "time.h"
#include "kernel.h" /* defines tz */
/*
* PCFS include files.
*/
#include "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;
union dosdate lastddate;
union dostime 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;
union dosdate *ddp;
union dostime *dtp;
{
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.
*/
if (lasttime != tvp->tv_sec) {
lasttime = tvp->tv_sec - (tz.tz_minuteswest * 60)
/* +- daylight savings time correction */;
lastdtime.dts.dt_2seconds = (lasttime % 60) >> 1;
lastdtime.dts.dt_minutes = (lasttime / 60) % 60;
lastdtime.dts.dt_hours = (lasttime / (60 * 60)) % 24;
/*
* 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 = lasttime / (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.dds.dd_day = days + 1;
lastddate.dds.dd_month = month+1;
/*
* 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.
*/
lastddate.dds.dd_year = year < 1980 ? 0 : year - 1980;
}
}
dtp->dti = lastdtime.dti;
ddp->ddi = lastddate.ddi;
}
/*
* 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))
union dosdate 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(ddp, dtp, tvp)
union dosdate *ddp;
union dostime *dtp;
struct timeval *tvp;
{
u_long seconds;
u_long month;
u_long yr;
u_long days;
u_short *months;
seconds = (dtp->dts.dt_2seconds << 1) +
(dtp->dts.dt_minutes * 60) +
(dtp->dts.dt_hours * 60 * 60);
/*
* If the year, month, and day from the last conversion
* are the same then use the saved value.
*/
if (lastdosdate.ddi != ddp->ddi) {
lastdosdate.ddi = ddp->ddi;
days = 0;
for (yr = 0; yr < ddp->dds.dd_year; yr++) {
days += yr & 0x03 ? 365 : 366;
}
months = yr & 0x03 ? regyear : leapyear;
/*
* Prevent going from 0 to 0xffffffff in the following
* loop.
*/
if (ddp->dds.dd_month == 0) {
printf("dos2unixtime(): month value out of range (%d)\n",
ddp->dds.dd_month);
ddp->dds.dd_month = 1;
}
for (month = 0; month < ddp->dds.dd_month-1; month++) {
days += months[month];
}
days += ddp->dds.dd_day - 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