msdosfs_conv.c revision 20138
120138Sbde/* $Id: msdosfs_conv.c,v 1.10 1996/09/19 18:20:43 nate Exp $ */ 22893Sdfr/* $NetBSD: msdosfs_conv.c,v 1.6.2.1 1994/08/30 02:27:57 cgd Exp $ */ 32893Sdfr 42893Sdfr/* 52893Sdfr * Written by Paul Popelka (paulp@uts.amdahl.com) 68876Srgrimes * 72893Sdfr * You can do anything you want with this software, just don't say you wrote 82893Sdfr * it, and don't remove this notice. 98876Srgrimes * 102893Sdfr * This software is provided "as is". 118876Srgrimes * 122893Sdfr * The author supplies this software to be publicly redistributed on the 132893Sdfr * understanding that the author is not responsible for the correct 142893Sdfr * functioning of this software in any circumstances and is not liable for 152893Sdfr * any damages caused by this software. 168876Srgrimes * 172893Sdfr * October 1992 182893Sdfr */ 192893Sdfr 202893Sdfr/* 212893Sdfr * System include files. 222893Sdfr */ 232893Sdfr#include <sys/param.h> 242893Sdfr#include <sys/time.h> 252893Sdfr#include <sys/kernel.h> /* defines tz */ 263152Sphk#include <sys/systm.h> /* defines tz */ 277465Sache#include <machine/clock.h> 282893Sdfr 292893Sdfr/* 302893Sdfr * MSDOSFS include files. 312893Sdfr */ 322893Sdfr#include <msdosfs/direntry.h> 332893Sdfr 342893Sdfr/* 357465Sache * Total number of days that have passed for each month in a regular year. 362893Sdfr */ 3712144Sphkstatic u_short regyear[] = { 387465Sache 31, 59, 90, 120, 151, 181, 397465Sache 212, 243, 273, 304, 334, 365 402893Sdfr}; 412893Sdfr 422893Sdfr/* 437465Sache * Total number of days that have passed for each month in a leap year. 442893Sdfr */ 4512144Sphkstatic u_short leapyear[] = { 467465Sache 31, 60, 91, 121, 152, 182, 477465Sache 213, 244, 274, 305, 335, 366 482893Sdfr}; 492893Sdfr 502893Sdfr/* 512893Sdfr * Variables used to remember parts of the last time conversion. Maybe we 522893Sdfr * can avoid a full conversion. 532893Sdfr */ 542893Sdfru_long lasttime; 552893Sdfru_long lastday; 562893Sdfru_short lastddate; 572893Sdfru_short lastdtime; 582893Sdfr 592893Sdfr/* 602893Sdfr * Convert the unix version of time to dos's idea of time to be used in 612893Sdfr * file timestamps. The passed in unix time is assumed to be in GMT. 622893Sdfr */ 632893Sdfrvoid 642893Sdfrunix2dostime(tsp, ddp, dtp) 652893Sdfr struct timespec *tsp; 662893Sdfr u_short *ddp; 672893Sdfr u_short *dtp; 682893Sdfr{ 692893Sdfr u_long t; 702893Sdfr u_long days; 712893Sdfr u_long inc; 722893Sdfr u_long year; 732893Sdfr u_long month; 742893Sdfr u_short *months; 752893Sdfr 762893Sdfr /* 772893Sdfr * If the time from the last conversion is the same as now, then 782893Sdfr * skip the computations and use the saved result. 792893Sdfr */ 8018397Snate t = tsp->tv_sec - (tz.tz_minuteswest * 60) 8115055Sache - (wall_cmos_clock ? adjkerntz : 0); 8215053Sache /* - daylight savings time correction */ 832893Sdfr if (lasttime != t) { 842893Sdfr lasttime = t; 852893Sdfr lastdtime = (((t % 60) >> 1) << DT_2SECONDS_SHIFT) 862893Sdfr + (((t / 60) % 60) << DT_MINUTES_SHIFT) 872893Sdfr + (((t / 3600) % 24) << DT_HOURS_SHIFT); 882893Sdfr 892893Sdfr /* 902893Sdfr * If the number of days since 1970 is the same as the last 912893Sdfr * time we did the computation then skip all this leap year 922893Sdfr * and month stuff. 932893Sdfr */ 942893Sdfr days = t / (24 * 60 * 60); 952893Sdfr if (days != lastday) { 962893Sdfr lastday = days; 972893Sdfr for (year = 1970;; year++) { 982893Sdfr inc = year & 0x03 ? 365 : 366; 992893Sdfr if (days < inc) 1002893Sdfr break; 1012893Sdfr days -= inc; 1022893Sdfr } 1032893Sdfr months = year & 0x03 ? regyear : leapyear; 10420138Sbde for (month = 0; days >= months[month]; month++) 1057465Sache ; 1067465Sache if (month > 0) 1077465Sache days -= months[month - 1]; 1082893Sdfr lastddate = ((days + 1) << DD_DAY_SHIFT) 1092893Sdfr + ((month + 1) << DD_MONTH_SHIFT); 1102893Sdfr /* 1112893Sdfr * Remember dos's idea of time is relative to 1980. 1122893Sdfr * unix's is relative to 1970. If somehow we get a 1132893Sdfr * time before 1980 then don't give totally crazy 1142893Sdfr * results. 1152893Sdfr */ 1162893Sdfr if (year > 1980) 1172893Sdfr lastddate += (year - 1980) << DD_YEAR_SHIFT; 1182893Sdfr } 1192893Sdfr } 1202893Sdfr *dtp = lastdtime; 1212893Sdfr *ddp = lastddate; 1222893Sdfr} 1232893Sdfr 1242893Sdfr/* 1252893Sdfr * The number of seconds between Jan 1, 1970 and Jan 1, 1980. In that 1262893Sdfr * interval there were 8 regular years and 2 leap years. 1272893Sdfr */ 1282893Sdfr#define SECONDSTO1980 (((8 * 365) + (2 * 366)) * (24 * 60 * 60)) 1292893Sdfr 1302893Sdfru_short lastdosdate; 1312893Sdfru_long lastseconds; 1322893Sdfr 1332893Sdfr/* 1342893Sdfr * Convert from dos' idea of time to unix'. This will probably only be 1352893Sdfr * called from the stat(), and fstat() system calls and so probably need 1362893Sdfr * not be too efficient. 1372893Sdfr */ 1382893Sdfrvoid 1392893Sdfrdos2unixtime(dd, dt, tsp) 1402893Sdfr u_short dd; 1412893Sdfr u_short dt; 1422893Sdfr struct timespec *tsp; 1432893Sdfr{ 1442893Sdfr u_long seconds; 14511921Sphk u_long month; 14611921Sphk u_long year; 1472893Sdfr u_long days; 1482893Sdfr u_short *months; 1492893Sdfr 1505083Sbde seconds = (((dt & DT_2SECONDS_MASK) >> DT_2SECONDS_SHIFT) << 1) 1512893Sdfr + ((dt & DT_MINUTES_MASK) >> DT_MINUTES_SHIFT) * 60 1522893Sdfr + ((dt & DT_HOURS_MASK) >> DT_HOURS_SHIFT) * 3600; 1532893Sdfr /* 1542893Sdfr * If the year, month, and day from the last conversion are the 1552893Sdfr * same then use the saved value. 1562893Sdfr */ 1572893Sdfr if (lastdosdate != dd) { 1582893Sdfr lastdosdate = dd; 1592893Sdfr days = 0; 1602893Sdfr year = (dd & DD_YEAR_MASK) >> DD_YEAR_SHIFT; 1617465Sache days = year * 365; 1627465Sache days += year / 4 + 1; /* add in leap days */ 1637465Sache if ((year & 0x03) == 0) 1647465Sache days--; /* if year is a leap year */ 1652893Sdfr months = year & 0x03 ? regyear : leapyear; 1662893Sdfr month = (dd & DD_MONTH_MASK) >> DD_MONTH_SHIFT; 1677465Sache if (month < 1 || month > 12) { 1683152Sphk printf( 1693152Sphk "dos2unixtime(): month value out of range (%ld)\n", 1703152Sphk month); 1712893Sdfr month = 1; 1722893Sdfr } 1737465Sache if (month > 1) 1747465Sache days += months[month - 2]; 1752893Sdfr days += ((dd & DD_DAY_MASK) >> DD_DAY_SHIFT) - 1; 1762893Sdfr lastseconds = (days * 24 * 60 * 60) + SECONDSTO1980; 1772893Sdfr } 17818397Snate tsp->tv_sec = seconds + lastseconds + (tz.tz_minuteswest * 60) 17915055Sache + adjkerntz; 18015053Sache /* + daylight savings time correction */ 18118397Snate tsp->tv_nsec = 0; 1822893Sdfr} 1832893Sdfr 1842893Sdfr/* 1852893Sdfr * Cheezy macros to do case detection and conversion for the ascii 1862893Sdfr * character set. DOESN'T work for ebcdic. 1872893Sdfr */ 1882893Sdfr#define isupper(c) (c >= 'A' && c <= 'Z') 1892893Sdfr#define islower(c) (c >= 'a' && c <= 'z') 1902893Sdfr#define toupper(c) (c & ~' ') 1912893Sdfr#define tolower(c) (c | ' ') 1922893Sdfr 1932893Sdfr/* 1942893Sdfr * DOS filenames are made of 2 parts, the name part and the extension part. 1952893Sdfr * The name part is 8 characters long and the extension part is 3 1962893Sdfr * characters long. They may contain trailing blanks if the name or 1972893Sdfr * extension are not long enough to fill their respective fields. 1982893Sdfr */ 1992893Sdfr 2002893Sdfr/* 2012893Sdfr * Convert a DOS filename to a unix filename. And, return the number of 2022893Sdfr * characters in the resulting unix filename excluding the terminating 2032893Sdfr * null. 2042893Sdfr */ 2052893Sdfrint 2062893Sdfrdos2unixfn(dn, un) 2072893Sdfr u_char dn[11]; 2082893Sdfr u_char *un; 2092893Sdfr{ 2102893Sdfr int i; 2112893Sdfr int ni; 2122893Sdfr int ei; 2132893Sdfr int thislong = 0; 2142893Sdfr u_char c; 2152893Sdfr u_char *origun = un; 2162893Sdfr 2172893Sdfr /* 2182893Sdfr * Find the last character in the name portion of the dos filename. 2192893Sdfr */ 2202893Sdfr for (ni = 7; ni >= 0; ni--) 2212893Sdfr if (dn[ni] != ' ') 2222893Sdfr break; 2232893Sdfr 2242893Sdfr /* 2252893Sdfr * Find the last character in the extension portion of the 2262893Sdfr * filename. 2272893Sdfr */ 2282893Sdfr for (ei = 10; ei >= 8; ei--) 2292893Sdfr if (dn[ei] != ' ') 2302893Sdfr break; 2312893Sdfr 2322893Sdfr /* 2332893Sdfr * Copy the name portion into the unix filename string. NOTE: DOS 2342893Sdfr * filenames are usually kept in upper case. To make it more unixy 2352893Sdfr * we convert all DOS filenames to lower case. Some may like this, 2362893Sdfr * some may not. 2372893Sdfr */ 2382893Sdfr for (i = 0; i <= ni; i++) { 2392893Sdfr c = dn[i]; 2402893Sdfr *un++ = isupper(c) ? tolower(c) : c; 2412893Sdfr thislong++; 2422893Sdfr } 2432893Sdfr 2442893Sdfr /* 2452893Sdfr * Now, if there is an extension then put in a period and copy in 2462893Sdfr * the extension. 2472893Sdfr */ 2482893Sdfr if (ei >= 8) { 2492893Sdfr *un++ = '.'; 2502893Sdfr thislong++; 2512893Sdfr for (i = 8; i <= ei; i++) { 2522893Sdfr c = dn[i]; 2532893Sdfr *un++ = isupper(c) ? tolower(c) : c; 2542893Sdfr thislong++; 2552893Sdfr } 2562893Sdfr } 2572893Sdfr *un++ = 0; 2582893Sdfr 2592893Sdfr /* 2602893Sdfr * If first char of the filename is SLOT_E5 (0x05), then the real 2612893Sdfr * first char of the filename should be 0xe5. But, they couldn't 2622893Sdfr * just have a 0xe5 mean 0xe5 because that is used to mean a freed 2632893Sdfr * directory slot. Another dos quirk. 2642893Sdfr */ 2652893Sdfr if (*origun == SLOT_E5) 2662893Sdfr *origun = 0xe5; 2672893Sdfr 2682893Sdfr return thislong; 2692893Sdfr} 2702893Sdfr 2712893Sdfr/* 2722893Sdfr * Convert a unix filename to a DOS filename. This function does not ensure 2732893Sdfr * that valid characters for a dos filename are supplied. 2742893Sdfr */ 2752893Sdfrvoid 2762893Sdfrunix2dosfn(un, dn, unlen) 2772893Sdfr u_char *un; 2782893Sdfr u_char dn[11]; 2792893Sdfr int unlen; 2802893Sdfr{ 2812893Sdfr int i; 2822893Sdfr u_char c; 2832893Sdfr 2842893Sdfr /* 2852893Sdfr * Fill the dos filename string with blanks. These are DOS's pad 2862893Sdfr * characters. 2872893Sdfr */ 2882893Sdfr for (i = 0; i <= 10; i++) 2892893Sdfr dn[i] = ' '; 2902893Sdfr 2912893Sdfr /* 2922893Sdfr * The filenames "." and ".." are handled specially, since they 2932893Sdfr * don't follow dos filename rules. 2942893Sdfr */ 2952893Sdfr if (un[0] == '.' && unlen == 1) { 2962893Sdfr dn[0] = '.'; 2972893Sdfr return; 2982893Sdfr } 2992893Sdfr if (un[0] == '.' && un[1] == '.' && unlen == 2) { 3002893Sdfr dn[0] = '.'; 3012893Sdfr dn[1] = '.'; 3022893Sdfr return; 3032893Sdfr } 3042893Sdfr 3052893Sdfr /* 3062893Sdfr * Copy the unix filename into the dos filename string upto the end 3072893Sdfr * of string, a '.', or 8 characters. Whichever happens first stops 3082893Sdfr * us. This forms the name portion of the dos filename. Fold to 3092893Sdfr * upper case. 3102893Sdfr */ 3112893Sdfr for (i = 0; i <= 7 && unlen && (c = *un) && c != '.'; i++) { 3122893Sdfr dn[i] = islower(c) ? toupper(c) : c; 3132893Sdfr un++; 3142893Sdfr unlen--; 3152893Sdfr } 3162893Sdfr 3172893Sdfr /* 3182893Sdfr * If the first char of the filename is 0xe5, then translate it to 3192893Sdfr * 0x05. This is because 0xe5 is the marker for a deleted 3202893Sdfr * directory slot. I guess this means you can't have filenames 3212893Sdfr * that start with 0x05. I suppose we should check for this and 3222893Sdfr * doing something about it. 3232893Sdfr */ 3242893Sdfr if (dn[0] == SLOT_DELETED) 3252893Sdfr dn[0] = SLOT_E5; 3262893Sdfr 3272893Sdfr /* 3282893Sdfr * Strip any further characters up to a '.' or the end of the 3292893Sdfr * string. 3302893Sdfr */ 3312893Sdfr while (unlen && (c = *un)) { 3322893Sdfr un++; 3332893Sdfr unlen--; 3342893Sdfr /* Make sure we've skipped over the dot before stopping. */ 3352893Sdfr if (c == '.') 3362893Sdfr break; 3372893Sdfr } 3382893Sdfr 3392893Sdfr /* 3402893Sdfr * Copy in the extension part of the name, if any. Force to upper 3412893Sdfr * case. Note that the extension is allowed to contain '.'s. 3422893Sdfr * Filenames in this form are probably inaccessable under dos. 3432893Sdfr */ 3442893Sdfr for (i = 8; i <= 10 && unlen && (c = *un); i++) { 3452893Sdfr dn[i] = islower(c) ? toupper(c) : c; 3462893Sdfr un++; 3472893Sdfr unlen--; 3482893Sdfr } 3492893Sdfr} 3502893Sdfr 3512893Sdfr/* 3522893Sdfr * Get rid of these macros before someone discovers we are using such 3532893Sdfr * hideous things. 3542893Sdfr */ 3552893Sdfr#undef isupper 3562893Sdfr#undef islower 3572893Sdfr#undef toupper 3582893Sdfr#undef tolower 359