msdosfs_conv.c revision 11921
111921Sphk/* $Id: msdosfs_conv.c,v 1.5 1995/05/30 08:07:36 rgrimes 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 */ 372893Sdfru_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 */ 452893Sdfru_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 */ 807465Sache t = tsp->ts_sec - (tz.tz_minuteswest * 60) - adjkerntz; 812893Sdfr /* +- daylight savings time correction */ ; 822893Sdfr if (lasttime != t) { 832893Sdfr lasttime = t; 842893Sdfr lastdtime = (((t % 60) >> 1) << DT_2SECONDS_SHIFT) 852893Sdfr + (((t / 60) % 60) << DT_MINUTES_SHIFT) 862893Sdfr + (((t / 3600) % 24) << DT_HOURS_SHIFT); 872893Sdfr 882893Sdfr /* 892893Sdfr * If the number of days since 1970 is the same as the last 902893Sdfr * time we did the computation then skip all this leap year 912893Sdfr * and month stuff. 922893Sdfr */ 932893Sdfr days = t / (24 * 60 * 60); 942893Sdfr if (days != lastday) { 952893Sdfr lastday = days; 962893Sdfr for (year = 1970;; year++) { 972893Sdfr inc = year & 0x03 ? 365 : 366; 982893Sdfr if (days < inc) 992893Sdfr break; 1002893Sdfr days -= inc; 1012893Sdfr } 1022893Sdfr months = year & 0x03 ? regyear : leapyear; 1037465Sache for (month = 0; days > months[month]; month++) 1047465Sache ; 1057465Sache if (month > 0) 1067465Sache days -= months[month - 1]; 1072893Sdfr lastddate = ((days + 1) << DD_DAY_SHIFT) 1082893Sdfr + ((month + 1) << DD_MONTH_SHIFT); 1092893Sdfr /* 1102893Sdfr * Remember dos's idea of time is relative to 1980. 1112893Sdfr * unix's is relative to 1970. If somehow we get a 1122893Sdfr * time before 1980 then don't give totally crazy 1132893Sdfr * results. 1142893Sdfr */ 1152893Sdfr if (year > 1980) 1162893Sdfr lastddate += (year - 1980) << DD_YEAR_SHIFT; 1172893Sdfr } 1182893Sdfr } 1192893Sdfr *dtp = lastdtime; 1202893Sdfr *ddp = lastddate; 1212893Sdfr} 1222893Sdfr 1232893Sdfr/* 1242893Sdfr * The number of seconds between Jan 1, 1970 and Jan 1, 1980. In that 1252893Sdfr * interval there were 8 regular years and 2 leap years. 1262893Sdfr */ 1272893Sdfr#define SECONDSTO1980 (((8 * 365) + (2 * 366)) * (24 * 60 * 60)) 1282893Sdfr 1292893Sdfru_short lastdosdate; 1302893Sdfru_long lastseconds; 1312893Sdfr 1322893Sdfr/* 1332893Sdfr * Convert from dos' idea of time to unix'. This will probably only be 1342893Sdfr * called from the stat(), and fstat() system calls and so probably need 1352893Sdfr * not be too efficient. 1362893Sdfr */ 1372893Sdfrvoid 1382893Sdfrdos2unixtime(dd, dt, tsp) 1392893Sdfr u_short dd; 1402893Sdfr u_short dt; 1412893Sdfr struct timespec *tsp; 1422893Sdfr{ 1432893Sdfr u_long seconds; 14411921Sphk u_long month; 14511921Sphk u_long year; 1462893Sdfr u_long days; 1472893Sdfr u_short *months; 1482893Sdfr 1495083Sbde seconds = (((dt & DT_2SECONDS_MASK) >> DT_2SECONDS_SHIFT) << 1) 1502893Sdfr + ((dt & DT_MINUTES_MASK) >> DT_MINUTES_SHIFT) * 60 1512893Sdfr + ((dt & DT_HOURS_MASK) >> DT_HOURS_SHIFT) * 3600; 1522893Sdfr /* 1532893Sdfr * If the year, month, and day from the last conversion are the 1542893Sdfr * same then use the saved value. 1552893Sdfr */ 1562893Sdfr if (lastdosdate != dd) { 1572893Sdfr lastdosdate = dd; 1582893Sdfr days = 0; 1592893Sdfr year = (dd & DD_YEAR_MASK) >> DD_YEAR_SHIFT; 1607465Sache days = year * 365; 1617465Sache days += year / 4 + 1; /* add in leap days */ 1627465Sache if ((year & 0x03) == 0) 1637465Sache days--; /* if year is a leap year */ 1642893Sdfr months = year & 0x03 ? regyear : leapyear; 1652893Sdfr month = (dd & DD_MONTH_MASK) >> DD_MONTH_SHIFT; 1667465Sache if (month < 1 || month > 12) { 1673152Sphk printf( 1683152Sphk "dos2unixtime(): month value out of range (%ld)\n", 1693152Sphk month); 1702893Sdfr month = 1; 1712893Sdfr } 1727465Sache if (month > 1) 1737465Sache days += months[month - 2]; 1742893Sdfr days += ((dd & DD_DAY_MASK) >> DD_DAY_SHIFT) - 1; 1752893Sdfr lastseconds = (days * 24 * 60 * 60) + SECONDSTO1980; 1762893Sdfr } 1772893Sdfr tsp->ts_sec = seconds + lastseconds + (tz.tz_minuteswest * 60) 1787465Sache + adjkerntz /* -+ daylight savings time correction */ ; 1792893Sdfr tsp->ts_nsec = 0; 1802893Sdfr} 1812893Sdfr 1822893Sdfr/* 1832893Sdfr * Cheezy macros to do case detection and conversion for the ascii 1842893Sdfr * character set. DOESN'T work for ebcdic. 1852893Sdfr */ 1862893Sdfr#define isupper(c) (c >= 'A' && c <= 'Z') 1872893Sdfr#define islower(c) (c >= 'a' && c <= 'z') 1882893Sdfr#define toupper(c) (c & ~' ') 1892893Sdfr#define tolower(c) (c | ' ') 1902893Sdfr 1912893Sdfr/* 1922893Sdfr * DOS filenames are made of 2 parts, the name part and the extension part. 1932893Sdfr * The name part is 8 characters long and the extension part is 3 1942893Sdfr * characters long. They may contain trailing blanks if the name or 1952893Sdfr * extension are not long enough to fill their respective fields. 1962893Sdfr */ 1972893Sdfr 1982893Sdfr/* 1992893Sdfr * Convert a DOS filename to a unix filename. And, return the number of 2002893Sdfr * characters in the resulting unix filename excluding the terminating 2012893Sdfr * null. 2022893Sdfr */ 2032893Sdfrint 2042893Sdfrdos2unixfn(dn, un) 2052893Sdfr u_char dn[11]; 2062893Sdfr u_char *un; 2072893Sdfr{ 2082893Sdfr int i; 2092893Sdfr int ni; 2102893Sdfr int ei; 2112893Sdfr int thislong = 0; 2122893Sdfr u_char c; 2132893Sdfr u_char *origun = un; 2142893Sdfr 2152893Sdfr /* 2162893Sdfr * Find the last character in the name portion of the dos filename. 2172893Sdfr */ 2182893Sdfr for (ni = 7; ni >= 0; ni--) 2192893Sdfr if (dn[ni] != ' ') 2202893Sdfr break; 2212893Sdfr 2222893Sdfr /* 2232893Sdfr * Find the last character in the extension portion of the 2242893Sdfr * filename. 2252893Sdfr */ 2262893Sdfr for (ei = 10; ei >= 8; ei--) 2272893Sdfr if (dn[ei] != ' ') 2282893Sdfr break; 2292893Sdfr 2302893Sdfr /* 2312893Sdfr * Copy the name portion into the unix filename string. NOTE: DOS 2322893Sdfr * filenames are usually kept in upper case. To make it more unixy 2332893Sdfr * we convert all DOS filenames to lower case. Some may like this, 2342893Sdfr * some may not. 2352893Sdfr */ 2362893Sdfr for (i = 0; i <= ni; i++) { 2372893Sdfr c = dn[i]; 2382893Sdfr *un++ = isupper(c) ? tolower(c) : c; 2392893Sdfr thislong++; 2402893Sdfr } 2412893Sdfr 2422893Sdfr /* 2432893Sdfr * Now, if there is an extension then put in a period and copy in 2442893Sdfr * the extension. 2452893Sdfr */ 2462893Sdfr if (ei >= 8) { 2472893Sdfr *un++ = '.'; 2482893Sdfr thislong++; 2492893Sdfr for (i = 8; i <= ei; i++) { 2502893Sdfr c = dn[i]; 2512893Sdfr *un++ = isupper(c) ? tolower(c) : c; 2522893Sdfr thislong++; 2532893Sdfr } 2542893Sdfr } 2552893Sdfr *un++ = 0; 2562893Sdfr 2572893Sdfr /* 2582893Sdfr * If first char of the filename is SLOT_E5 (0x05), then the real 2592893Sdfr * first char of the filename should be 0xe5. But, they couldn't 2602893Sdfr * just have a 0xe5 mean 0xe5 because that is used to mean a freed 2612893Sdfr * directory slot. Another dos quirk. 2622893Sdfr */ 2632893Sdfr if (*origun == SLOT_E5) 2642893Sdfr *origun = 0xe5; 2652893Sdfr 2662893Sdfr return thislong; 2672893Sdfr} 2682893Sdfr 2692893Sdfr/* 2702893Sdfr * Convert a unix filename to a DOS filename. This function does not ensure 2712893Sdfr * that valid characters for a dos filename are supplied. 2722893Sdfr */ 2732893Sdfrvoid 2742893Sdfrunix2dosfn(un, dn, unlen) 2752893Sdfr u_char *un; 2762893Sdfr u_char dn[11]; 2772893Sdfr int unlen; 2782893Sdfr{ 2792893Sdfr int i; 2802893Sdfr u_char c; 2812893Sdfr 2822893Sdfr /* 2832893Sdfr * Fill the dos filename string with blanks. These are DOS's pad 2842893Sdfr * characters. 2852893Sdfr */ 2862893Sdfr for (i = 0; i <= 10; i++) 2872893Sdfr dn[i] = ' '; 2882893Sdfr 2892893Sdfr /* 2902893Sdfr * The filenames "." and ".." are handled specially, since they 2912893Sdfr * don't follow dos filename rules. 2922893Sdfr */ 2932893Sdfr if (un[0] == '.' && unlen == 1) { 2942893Sdfr dn[0] = '.'; 2952893Sdfr return; 2962893Sdfr } 2972893Sdfr if (un[0] == '.' && un[1] == '.' && unlen == 2) { 2982893Sdfr dn[0] = '.'; 2992893Sdfr dn[1] = '.'; 3002893Sdfr return; 3012893Sdfr } 3022893Sdfr 3032893Sdfr /* 3042893Sdfr * Copy the unix filename into the dos filename string upto the end 3052893Sdfr * of string, a '.', or 8 characters. Whichever happens first stops 3062893Sdfr * us. This forms the name portion of the dos filename. Fold to 3072893Sdfr * upper case. 3082893Sdfr */ 3092893Sdfr for (i = 0; i <= 7 && unlen && (c = *un) && c != '.'; i++) { 3102893Sdfr dn[i] = islower(c) ? toupper(c) : c; 3112893Sdfr un++; 3122893Sdfr unlen--; 3132893Sdfr } 3142893Sdfr 3152893Sdfr /* 3162893Sdfr * If the first char of the filename is 0xe5, then translate it to 3172893Sdfr * 0x05. This is because 0xe5 is the marker for a deleted 3182893Sdfr * directory slot. I guess this means you can't have filenames 3192893Sdfr * that start with 0x05. I suppose we should check for this and 3202893Sdfr * doing something about it. 3212893Sdfr */ 3222893Sdfr if (dn[0] == SLOT_DELETED) 3232893Sdfr dn[0] = SLOT_E5; 3242893Sdfr 3252893Sdfr /* 3262893Sdfr * Strip any further characters up to a '.' or the end of the 3272893Sdfr * string. 3282893Sdfr */ 3292893Sdfr while (unlen && (c = *un)) { 3302893Sdfr un++; 3312893Sdfr unlen--; 3322893Sdfr /* Make sure we've skipped over the dot before stopping. */ 3332893Sdfr if (c == '.') 3342893Sdfr break; 3352893Sdfr } 3362893Sdfr 3372893Sdfr /* 3382893Sdfr * Copy in the extension part of the name, if any. Force to upper 3392893Sdfr * case. Note that the extension is allowed to contain '.'s. 3402893Sdfr * Filenames in this form are probably inaccessable under dos. 3412893Sdfr */ 3422893Sdfr for (i = 8; i <= 10 && unlen && (c = *un); i++) { 3432893Sdfr dn[i] = islower(c) ? toupper(c) : c; 3442893Sdfr un++; 3452893Sdfr unlen--; 3462893Sdfr } 3472893Sdfr} 3482893Sdfr 3492893Sdfr/* 3502893Sdfr * Get rid of these macros before someone discovers we are using such 3512893Sdfr * hideous things. 3522893Sdfr */ 3532893Sdfr#undef isupper 3542893Sdfr#undef islower 3552893Sdfr#undef toupper 3562893Sdfr#undef tolower 357