msdosfs_conv.c revision 18397
1226031Sstas/* $Id: msdosfs_conv.c,v 1.9 1996/04/05 18:59:06 ache Exp $ */ 2226031Sstas/* $NetBSD: msdosfs_conv.c,v 1.6.2.1 1994/08/30 02:27:57 cgd Exp $ */ 3226031Sstas 4226031Sstas/* 5226031Sstas * Written by Paul Popelka (paulp@uts.amdahl.com) 6226031Sstas * 7226031Sstas * You can do anything you want with this software, just don't say you wrote 8226031Sstas * it, and don't remove this notice. 9226031Sstas * 10226031Sstas * This software is provided "as is". 11226031Sstas * 12226031Sstas * The author supplies this software to be publicly redistributed on the 13226031Sstas * understanding that the author is not responsible for the correct 14226031Sstas * functioning of this software in any circumstances and is not liable for 15226031Sstas * any damages caused by this software. 16226031Sstas * 17226031Sstas * October 1992 18226031Sstas */ 19226031Sstas 20226031Sstas/* 21226031Sstas * System include files. 22226031Sstas */ 23226031Sstas#include <sys/param.h> 24226031Sstas#include <sys/time.h> 25226031Sstas#include <sys/kernel.h> /* defines tz */ 26226031Sstas#include <sys/systm.h> /* defines tz */ 27226031Sstas#include <machine/clock.h> 28226031Sstas 29226031Sstas/* 30226031Sstas * MSDOSFS include files. 31226031Sstas */ 32226031Sstas#include <msdosfs/direntry.h> 33226031Sstas 34226031Sstas/* 35226031Sstas * Total number of days that have passed for each month in a regular year. 36226031Sstas */ 37226031Sstasstatic u_short regyear[] = { 38226031Sstas 31, 59, 90, 120, 151, 181, 39226031Sstas 212, 243, 273, 304, 334, 365 40226031Sstas}; 41226031Sstas 42226031Sstas/* 43226031Sstas * Total number of days that have passed for each month in a leap year. 44226031Sstas */ 45226031Sstasstatic u_short leapyear[] = { 46226031Sstas 31, 60, 91, 121, 152, 182, 47226031Sstas 213, 244, 274, 305, 335, 366 48226031Sstas}; 49226031Sstas 50226031Sstas/* 51226031Sstas * Variables used to remember parts of the last time conversion. Maybe we 52226031Sstas * can avoid a full conversion. 53226031Sstas */ 54226031Sstasu_long lasttime; 55226031Sstasu_long lastday; 56226031Sstasu_short lastddate; 57226031Sstasu_short lastdtime; 58226031Sstas 59226031Sstas/* 60226031Sstas * Convert the unix version of time to dos's idea of time to be used in 61226031Sstas * file timestamps. The passed in unix time is assumed to be in GMT. 62226031Sstas */ 63226031Sstasvoid 64226031Sstasunix2dostime(tsp, ddp, dtp) 65226031Sstas struct timespec *tsp; 66226031Sstas u_short *ddp; 67226031Sstas u_short *dtp; 68226031Sstas{ 69226031Sstas u_long t; 70226031Sstas u_long days; 71226031Sstas u_long inc; 72226031Sstas u_long year; 73226031Sstas u_long month; 74226031Sstas u_short *months; 75226031Sstas 76226031Sstas /* 77226031Sstas * If the time from the last conversion is the same as now, then 78226031Sstas * skip the computations and use the saved result. 79226031Sstas */ 80226031Sstas t = tsp->tv_sec - (tz.tz_minuteswest * 60) 81226031Sstas - (wall_cmos_clock ? adjkerntz : 0); 82226031Sstas /* - daylight savings time correction */ 83226031Sstas if (lasttime != t) { 84226031Sstas lasttime = t; 85226031Sstas lastdtime = (((t % 60) >> 1) << DT_2SECONDS_SHIFT) 86226031Sstas + (((t / 60) % 60) << DT_MINUTES_SHIFT) 87226031Sstas + (((t / 3600) % 24) << DT_HOURS_SHIFT); 88226031Sstas 89226031Sstas /* 90226031Sstas * If the number of days since 1970 is the same as the last 91226031Sstas * time we did the computation then skip all this leap year 92226031Sstas * and month stuff. 93226031Sstas */ 94226031Sstas days = t / (24 * 60 * 60); 95226031Sstas if (days != lastday) { 96226031Sstas lastday = days; 97226031Sstas for (year = 1970;; year++) { 98226031Sstas inc = year & 0x03 ? 365 : 366; 99226031Sstas if (days < inc) 100226031Sstas break; 101226031Sstas days -= inc; 102226031Sstas } 103226031Sstas months = year & 0x03 ? regyear : leapyear; 104226031Sstas for (month = 0; days > months[month]; month++) 105226031Sstas ; 106226031Sstas if (month > 0) 107226031Sstas days -= months[month - 1]; 108226031Sstas lastddate = ((days + 1) << DD_DAY_SHIFT) 109226031Sstas + ((month + 1) << DD_MONTH_SHIFT); 110226031Sstas /* 111226031Sstas * Remember dos's idea of time is relative to 1980. 112226031Sstas * unix's is relative to 1970. If somehow we get a 113226031Sstas * time before 1980 then don't give totally crazy 114226031Sstas * results. 115226031Sstas */ 116226031Sstas if (year > 1980) 117226031Sstas lastddate += (year - 1980) << DD_YEAR_SHIFT; 118226031Sstas } 119226031Sstas } 120226031Sstas *dtp = lastdtime; 121226031Sstas *ddp = lastddate; 122226031Sstas} 123226031Sstas 124226031Sstas/* 125226031Sstas * The number of seconds between Jan 1, 1970 and Jan 1, 1980. In that 126226031Sstas * interval there were 8 regular years and 2 leap years. 127226031Sstas */ 128226031Sstas#define SECONDSTO1980 (((8 * 365) + (2 * 366)) * (24 * 60 * 60)) 129226031Sstas 130226031Sstasu_short lastdosdate; 131226031Sstasu_long lastseconds; 132226031Sstas 133226031Sstas/* 134226031Sstas * Convert from dos' idea of time to unix'. This will probably only be 135226031Sstas * called from the stat(), and fstat() system calls and so probably need 136226031Sstas * not be too efficient. 137226031Sstas */ 138226031Sstasvoid 139226031Sstasdos2unixtime(dd, dt, tsp) 140226031Sstas u_short dd; 141226031Sstas u_short dt; 142226031Sstas struct timespec *tsp; 143226031Sstas{ 144226031Sstas u_long seconds; 145226031Sstas u_long month; 146226031Sstas u_long year; 147226031Sstas u_long days; 148226031Sstas u_short *months; 149226031Sstas 150226031Sstas seconds = (((dt & DT_2SECONDS_MASK) >> DT_2SECONDS_SHIFT) << 1) 151226031Sstas + ((dt & DT_MINUTES_MASK) >> DT_MINUTES_SHIFT) * 60 152226031Sstas + ((dt & DT_HOURS_MASK) >> DT_HOURS_SHIFT) * 3600; 153226031Sstas /* 154226031Sstas * If the year, month, and day from the last conversion are the 155226031Sstas * same then use the saved value. 156226031Sstas */ 157226031Sstas if (lastdosdate != dd) { 158226031Sstas lastdosdate = dd; 159226031Sstas days = 0; 160226031Sstas year = (dd & DD_YEAR_MASK) >> DD_YEAR_SHIFT; 161226031Sstas days = year * 365; 162226031Sstas days += year / 4 + 1; /* add in leap days */ 163226031Sstas if ((year & 0x03) == 0) 164226031Sstas days--; /* if year is a leap year */ 165226031Sstas months = year & 0x03 ? regyear : leapyear; 166226031Sstas month = (dd & DD_MONTH_MASK) >> DD_MONTH_SHIFT; 167226031Sstas if (month < 1 || month > 12) { 168226031Sstas printf( 169226031Sstas "dos2unixtime(): month value out of range (%ld)\n", 170226031Sstas month); 171226031Sstas month = 1; 172226031Sstas } 173226031Sstas if (month > 1) 174226031Sstas days += months[month - 2]; 175226031Sstas days += ((dd & DD_DAY_MASK) >> DD_DAY_SHIFT) - 1; 176226031Sstas lastseconds = (days * 24 * 60 * 60) + SECONDSTO1980; 177226031Sstas } 178226031Sstas tsp->tv_sec = seconds + lastseconds + (tz.tz_minuteswest * 60) 179226031Sstas + adjkerntz; 180226031Sstas /* + daylight savings time correction */ 181226031Sstas tsp->tv_nsec = 0; 182226031Sstas} 183226031Sstas 184226031Sstas/* 185226031Sstas * Cheezy macros to do case detection and conversion for the ascii 186226031Sstas * character set. DOESN'T work for ebcdic. 187226031Sstas */ 188226031Sstas#define isupper(c) (c >= 'A' && c <= 'Z') 189226031Sstas#define islower(c) (c >= 'a' && c <= 'z') 190226031Sstas#define toupper(c) (c & ~' ') 191226031Sstas#define tolower(c) (c | ' ') 192226031Sstas 193226031Sstas/* 194226031Sstas * DOS filenames are made of 2 parts, the name part and the extension part. 195226031Sstas * The name part is 8 characters long and the extension part is 3 196226031Sstas * characters long. They may contain trailing blanks if the name or 197226031Sstas * extension are not long enough to fill their respective fields. 198226031Sstas */ 199226031Sstas 200226031Sstas/* 201226031Sstas * Convert a DOS filename to a unix filename. And, return the number of 202226031Sstas * characters in the resulting unix filename excluding the terminating 203226031Sstas * null. 204226031Sstas */ 205226031Sstasint 206226031Sstasdos2unixfn(dn, un) 207226031Sstas u_char dn[11]; 208226031Sstas u_char *un; 209226031Sstas{ 210226031Sstas int i; 211226031Sstas int ni; 212226031Sstas int ei; 213226031Sstas int thislong = 0; 214226031Sstas u_char c; 215226031Sstas u_char *origun = un; 216226031Sstas 217226031Sstas /* 218226031Sstas * Find the last character in the name portion of the dos filename. 219226031Sstas */ 220226031Sstas for (ni = 7; ni >= 0; ni--) 221226031Sstas if (dn[ni] != ' ') 222226031Sstas break; 223226031Sstas 224226031Sstas /* 225226031Sstas * Find the last character in the extension portion of the 226226031Sstas * filename. 227226031Sstas */ 228226031Sstas for (ei = 10; ei >= 8; ei--) 229226031Sstas if (dn[ei] != ' ') 230226031Sstas break; 231226031Sstas 232226031Sstas /* 233226031Sstas * Copy the name portion into the unix filename string. NOTE: DOS 234226031Sstas * filenames are usually kept in upper case. To make it more unixy 235226031Sstas * we convert all DOS filenames to lower case. Some may like this, 236226031Sstas * some may not. 237226031Sstas */ 238226031Sstas for (i = 0; i <= ni; i++) { 239226031Sstas c = dn[i]; 240226031Sstas *un++ = isupper(c) ? tolower(c) : c; 241226031Sstas thislong++; 242226031Sstas } 243226031Sstas 244226031Sstas /* 245226031Sstas * Now, if there is an extension then put in a period and copy in 246226031Sstas * the extension. 247226031Sstas */ 248226031Sstas if (ei >= 8) { 249226031Sstas *un++ = '.'; 250226031Sstas thislong++; 251226031Sstas for (i = 8; i <= ei; i++) { 252226031Sstas c = dn[i]; 253226031Sstas *un++ = isupper(c) ? tolower(c) : c; 254226031Sstas thislong++; 255226031Sstas } 256226031Sstas } 257226031Sstas *un++ = 0; 258226031Sstas 259226031Sstas /* 260226031Sstas * If first char of the filename is SLOT_E5 (0x05), then the real 261226031Sstas * first char of the filename should be 0xe5. But, they couldn't 262226031Sstas * just have a 0xe5 mean 0xe5 because that is used to mean a freed 263226031Sstas * directory slot. Another dos quirk. 264226031Sstas */ 265226031Sstas if (*origun == SLOT_E5) 266226031Sstas *origun = 0xe5; 267226031Sstas 268226031Sstas return thislong; 269226031Sstas} 270226031Sstas 271226031Sstas/* 272226031Sstas * Convert a unix filename to a DOS filename. This function does not ensure 273226031Sstas * that valid characters for a dos filename are supplied. 274226031Sstas */ 275226031Sstasvoid 276226031Sstasunix2dosfn(un, dn, unlen) 277226031Sstas u_char *un; 278226031Sstas u_char dn[11]; 279226031Sstas int unlen; 280226031Sstas{ 281226031Sstas int i; 282226031Sstas u_char c; 283226031Sstas 284226031Sstas /* 285226031Sstas * Fill the dos filename string with blanks. These are DOS's pad 286226031Sstas * characters. 287226031Sstas */ 288226031Sstas for (i = 0; i <= 10; i++) 289226031Sstas dn[i] = ' '; 290226031Sstas 291226031Sstas /* 292226031Sstas * The filenames "." and ".." are handled specially, since they 293226031Sstas * don't follow dos filename rules. 294226031Sstas */ 295226031Sstas if (un[0] == '.' && unlen == 1) { 296226031Sstas dn[0] = '.'; 297226031Sstas return; 298226031Sstas } 299226031Sstas if (un[0] == '.' && un[1] == '.' && unlen == 2) { 300226031Sstas dn[0] = '.'; 301226031Sstas dn[1] = '.'; 302226031Sstas return; 303226031Sstas } 304226031Sstas 305226031Sstas /* 306226031Sstas * Copy the unix filename into the dos filename string upto the end 307226031Sstas * of string, a '.', or 8 characters. Whichever happens first stops 308226031Sstas * us. This forms the name portion of the dos filename. Fold to 309226031Sstas * upper case. 310226031Sstas */ 311226031Sstas for (i = 0; i <= 7 && unlen && (c = *un) && c != '.'; i++) { 312226031Sstas dn[i] = islower(c) ? toupper(c) : c; 313226031Sstas un++; 314226031Sstas unlen--; 315226031Sstas } 316226031Sstas 317226031Sstas /* 318226031Sstas * If the first char of the filename is 0xe5, then translate it to 319226031Sstas * 0x05. This is because 0xe5 is the marker for a deleted 320226031Sstas * directory slot. I guess this means you can't have filenames 321226031Sstas * that start with 0x05. I suppose we should check for this and 322226031Sstas * doing something about it. 323226031Sstas */ 324226031Sstas if (dn[0] == SLOT_DELETED) 325226031Sstas dn[0] = SLOT_E5; 326226031Sstas 327226031Sstas /* 328226031Sstas * Strip any further characters up to a '.' or the end of the 329226031Sstas * string. 330226031Sstas */ 331226031Sstas while (unlen && (c = *un)) { 332226031Sstas un++; 333226031Sstas unlen--; 334226031Sstas /* Make sure we've skipped over the dot before stopping. */ 335226031Sstas if (c == '.') 336226031Sstas break; 337226031Sstas } 338226031Sstas 339226031Sstas /* 340226031Sstas * Copy in the extension part of the name, if any. Force to upper 341226031Sstas * case. Note that the extension is allowed to contain '.'s. 342226031Sstas * Filenames in this form are probably inaccessable under dos. 343226031Sstas */ 344226031Sstas for (i = 8; i <= 10 && unlen && (c = *un); i++) { 345226031Sstas dn[i] = islower(c) ? toupper(c) : c; 346226031Sstas un++; 347226031Sstas unlen--; 348226031Sstas } 349226031Sstas} 350226031Sstas 351226031Sstas/* 352226031Sstas * Get rid of these macros before someone discovers we are using such 353226031Sstas * hideous things. 354226031Sstas */ 355226031Sstas#undef isupper 356226031Sstas#undef islower 357226031Sstas#undef toupper 358226031Sstas#undef tolower 359226031Sstas