msdosfs_conv.c revision 3152
13152Sphk/* $Id: msdosfs_conv.c,v 1.1 1994/09/19 15:41:40 dfr 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) 62893Sdfr * 72893Sdfr * You can do anything you want with this software, just don't say you wrote 82893Sdfr * it, and don't remove this notice. 92893Sdfr * 102893Sdfr * This software is provided "as is". 112893Sdfr * 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. 162893Sdfr * 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 */ 272893Sdfr 282893Sdfr/* 292893Sdfr * MSDOSFS include files. 302893Sdfr */ 312893Sdfr#include <msdosfs/direntry.h> 322893Sdfr 332893Sdfr/* 342893Sdfr * Days in each month in a regular year. 352893Sdfr */ 362893Sdfru_short regyear[] = { 372893Sdfr 31, 28, 31, 30, 31, 30, 382893Sdfr 31, 31, 30, 31, 30, 31 392893Sdfr}; 402893Sdfr 412893Sdfr/* 422893Sdfr * Days in each month in a leap year. 432893Sdfr */ 442893Sdfru_short leapyear[] = { 452893Sdfr 31, 29, 31, 30, 31, 30, 462893Sdfr 31, 31, 30, 31, 30, 31 472893Sdfr}; 482893Sdfr 492893Sdfr/* 502893Sdfr * Variables used to remember parts of the last time conversion. Maybe we 512893Sdfr * can avoid a full conversion. 522893Sdfr */ 532893Sdfru_long lasttime; 542893Sdfru_long lastday; 552893Sdfru_short lastddate; 562893Sdfru_short lastdtime; 572893Sdfr 582893Sdfr/* 592893Sdfr * Convert the unix version of time to dos's idea of time to be used in 602893Sdfr * file timestamps. The passed in unix time is assumed to be in GMT. 612893Sdfr */ 622893Sdfrvoid 632893Sdfrunix2dostime(tsp, ddp, dtp) 642893Sdfr struct timespec *tsp; 652893Sdfr u_short *ddp; 662893Sdfr u_short *dtp; 672893Sdfr{ 682893Sdfr u_long t; 692893Sdfr u_long days; 702893Sdfr u_long inc; 712893Sdfr u_long year; 722893Sdfr u_long month; 732893Sdfr u_short *months; 742893Sdfr 752893Sdfr /* 762893Sdfr * If the time from the last conversion is the same as now, then 772893Sdfr * skip the computations and use the saved result. 782893Sdfr */ 792893Sdfr t = tsp->ts_sec - (tz.tz_minuteswest * 60) 802893Sdfr /* +- daylight savings time correction */ ; 812893Sdfr if (lasttime != t) { 822893Sdfr lasttime = t; 832893Sdfr lastdtime = (((t % 60) >> 1) << DT_2SECONDS_SHIFT) 842893Sdfr + (((t / 60) % 60) << DT_MINUTES_SHIFT) 852893Sdfr + (((t / 3600) % 24) << DT_HOURS_SHIFT); 862893Sdfr 872893Sdfr /* 882893Sdfr * If the number of days since 1970 is the same as the last 892893Sdfr * time we did the computation then skip all this leap year 902893Sdfr * and month stuff. 912893Sdfr */ 922893Sdfr days = t / (24 * 60 * 60); 932893Sdfr if (days != lastday) { 942893Sdfr lastday = days; 952893Sdfr for (year = 1970;; year++) { 962893Sdfr inc = year & 0x03 ? 365 : 366; 972893Sdfr if (days < inc) 982893Sdfr break; 992893Sdfr days -= inc; 1002893Sdfr } 1012893Sdfr months = year & 0x03 ? regyear : leapyear; 1022893Sdfr for (month = 0; month < 12; month++) { 1032893Sdfr if (days < months[month]) 1042893Sdfr break; 1052893Sdfr days -= months[month]; 1062893Sdfr } 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; 1442893Sdfr u_long m, month; 1452893Sdfr u_long y, year; 1462893Sdfr u_long days; 1472893Sdfr u_short *months; 1482893Sdfr 1492893Sdfr seconds = ((dt & DT_2SECONDS_MASK) >> DT_2SECONDS_SHIFT) 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; 1602893Sdfr for (y = 0; y < year; y++) { 1612893Sdfr days += y & 0x03 ? 365 : 366; 1622893Sdfr } 1632893Sdfr months = year & 0x03 ? regyear : leapyear; 1642893Sdfr /* 1652893Sdfr * Prevent going from 0 to 0xffffffff in the following 1662893Sdfr * loop. 1672893Sdfr */ 1682893Sdfr month = (dd & DD_MONTH_MASK) >> DD_MONTH_SHIFT; 1692893Sdfr if (month == 0) { 1703152Sphk printf( 1713152Sphk "dos2unixtime(): month value out of range (%ld)\n", 1723152Sphk month); 1732893Sdfr month = 1; 1742893Sdfr } 1752893Sdfr for (m = 0; m < month - 1; m++) { 1762893Sdfr days += months[m]; 1772893Sdfr } 1782893Sdfr days += ((dd & DD_DAY_MASK) >> DD_DAY_SHIFT) - 1; 1792893Sdfr lastseconds = (days * 24 * 60 * 60) + SECONDSTO1980; 1802893Sdfr } 1812893Sdfr tsp->ts_sec = seconds + lastseconds + (tz.tz_minuteswest * 60) 1822893Sdfr /* -+ daylight savings time correction */ ; 1832893Sdfr tsp->ts_nsec = 0; 1842893Sdfr} 1852893Sdfr 1862893Sdfr/* 1872893Sdfr * Cheezy macros to do case detection and conversion for the ascii 1882893Sdfr * character set. DOESN'T work for ebcdic. 1892893Sdfr */ 1902893Sdfr#define isupper(c) (c >= 'A' && c <= 'Z') 1912893Sdfr#define islower(c) (c >= 'a' && c <= 'z') 1922893Sdfr#define toupper(c) (c & ~' ') 1932893Sdfr#define tolower(c) (c | ' ') 1942893Sdfr 1952893Sdfr/* 1962893Sdfr * DOS filenames are made of 2 parts, the name part and the extension part. 1972893Sdfr * The name part is 8 characters long and the extension part is 3 1982893Sdfr * characters long. They may contain trailing blanks if the name or 1992893Sdfr * extension are not long enough to fill their respective fields. 2002893Sdfr */ 2012893Sdfr 2022893Sdfr/* 2032893Sdfr * Convert a DOS filename to a unix filename. And, return the number of 2042893Sdfr * characters in the resulting unix filename excluding the terminating 2052893Sdfr * null. 2062893Sdfr */ 2072893Sdfrint 2082893Sdfrdos2unixfn(dn, un) 2092893Sdfr u_char dn[11]; 2102893Sdfr u_char *un; 2112893Sdfr{ 2122893Sdfr int i; 2132893Sdfr int ni; 2142893Sdfr int ei; 2152893Sdfr int thislong = 0; 2162893Sdfr u_char c; 2172893Sdfr u_char *origun = un; 2182893Sdfr 2192893Sdfr /* 2202893Sdfr * Find the last character in the name portion of the dos filename. 2212893Sdfr */ 2222893Sdfr for (ni = 7; ni >= 0; ni--) 2232893Sdfr if (dn[ni] != ' ') 2242893Sdfr break; 2252893Sdfr 2262893Sdfr /* 2272893Sdfr * Find the last character in the extension portion of the 2282893Sdfr * filename. 2292893Sdfr */ 2302893Sdfr for (ei = 10; ei >= 8; ei--) 2312893Sdfr if (dn[ei] != ' ') 2322893Sdfr break; 2332893Sdfr 2342893Sdfr /* 2352893Sdfr * Copy the name portion into the unix filename string. NOTE: DOS 2362893Sdfr * filenames are usually kept in upper case. To make it more unixy 2372893Sdfr * we convert all DOS filenames to lower case. Some may like this, 2382893Sdfr * some may not. 2392893Sdfr */ 2402893Sdfr for (i = 0; i <= ni; i++) { 2412893Sdfr c = dn[i]; 2422893Sdfr *un++ = isupper(c) ? tolower(c) : c; 2432893Sdfr thislong++; 2442893Sdfr } 2452893Sdfr 2462893Sdfr /* 2472893Sdfr * Now, if there is an extension then put in a period and copy in 2482893Sdfr * the extension. 2492893Sdfr */ 2502893Sdfr if (ei >= 8) { 2512893Sdfr *un++ = '.'; 2522893Sdfr thislong++; 2532893Sdfr for (i = 8; i <= ei; i++) { 2542893Sdfr c = dn[i]; 2552893Sdfr *un++ = isupper(c) ? tolower(c) : c; 2562893Sdfr thislong++; 2572893Sdfr } 2582893Sdfr } 2592893Sdfr *un++ = 0; 2602893Sdfr 2612893Sdfr /* 2622893Sdfr * If first char of the filename is SLOT_E5 (0x05), then the real 2632893Sdfr * first char of the filename should be 0xe5. But, they couldn't 2642893Sdfr * just have a 0xe5 mean 0xe5 because that is used to mean a freed 2652893Sdfr * directory slot. Another dos quirk. 2662893Sdfr */ 2672893Sdfr if (*origun == SLOT_E5) 2682893Sdfr *origun = 0xe5; 2692893Sdfr 2702893Sdfr return thislong; 2712893Sdfr} 2722893Sdfr 2732893Sdfr/* 2742893Sdfr * Convert a unix filename to a DOS filename. This function does not ensure 2752893Sdfr * that valid characters for a dos filename are supplied. 2762893Sdfr */ 2772893Sdfrvoid 2782893Sdfrunix2dosfn(un, dn, unlen) 2792893Sdfr u_char *un; 2802893Sdfr u_char dn[11]; 2812893Sdfr int unlen; 2822893Sdfr{ 2832893Sdfr int i; 2842893Sdfr u_char c; 2852893Sdfr 2862893Sdfr /* 2872893Sdfr * Fill the dos filename string with blanks. These are DOS's pad 2882893Sdfr * characters. 2892893Sdfr */ 2902893Sdfr for (i = 0; i <= 10; i++) 2912893Sdfr dn[i] = ' '; 2922893Sdfr 2932893Sdfr /* 2942893Sdfr * The filenames "." and ".." are handled specially, since they 2952893Sdfr * don't follow dos filename rules. 2962893Sdfr */ 2972893Sdfr if (un[0] == '.' && unlen == 1) { 2982893Sdfr dn[0] = '.'; 2992893Sdfr return; 3002893Sdfr } 3012893Sdfr if (un[0] == '.' && un[1] == '.' && unlen == 2) { 3022893Sdfr dn[0] = '.'; 3032893Sdfr dn[1] = '.'; 3042893Sdfr return; 3052893Sdfr } 3062893Sdfr 3072893Sdfr /* 3082893Sdfr * Copy the unix filename into the dos filename string upto the end 3092893Sdfr * of string, a '.', or 8 characters. Whichever happens first stops 3102893Sdfr * us. This forms the name portion of the dos filename. Fold to 3112893Sdfr * upper case. 3122893Sdfr */ 3132893Sdfr for (i = 0; i <= 7 && unlen && (c = *un) && c != '.'; i++) { 3142893Sdfr dn[i] = islower(c) ? toupper(c) : c; 3152893Sdfr un++; 3162893Sdfr unlen--; 3172893Sdfr } 3182893Sdfr 3192893Sdfr /* 3202893Sdfr * If the first char of the filename is 0xe5, then translate it to 3212893Sdfr * 0x05. This is because 0xe5 is the marker for a deleted 3222893Sdfr * directory slot. I guess this means you can't have filenames 3232893Sdfr * that start with 0x05. I suppose we should check for this and 3242893Sdfr * doing something about it. 3252893Sdfr */ 3262893Sdfr if (dn[0] == SLOT_DELETED) 3272893Sdfr dn[0] = SLOT_E5; 3282893Sdfr 3292893Sdfr /* 3302893Sdfr * Strip any further characters up to a '.' or the end of the 3312893Sdfr * string. 3322893Sdfr */ 3332893Sdfr while (unlen && (c = *un)) { 3342893Sdfr un++; 3352893Sdfr unlen--; 3362893Sdfr /* Make sure we've skipped over the dot before stopping. */ 3372893Sdfr if (c == '.') 3382893Sdfr break; 3392893Sdfr } 3402893Sdfr 3412893Sdfr /* 3422893Sdfr * Copy in the extension part of the name, if any. Force to upper 3432893Sdfr * case. Note that the extension is allowed to contain '.'s. 3442893Sdfr * Filenames in this form are probably inaccessable under dos. 3452893Sdfr */ 3462893Sdfr for (i = 8; i <= 10 && unlen && (c = *un); i++) { 3472893Sdfr dn[i] = islower(c) ? toupper(c) : c; 3482893Sdfr un++; 3492893Sdfr unlen--; 3502893Sdfr } 3512893Sdfr} 3522893Sdfr 3532893Sdfr/* 3542893Sdfr * Get rid of these macros before someone discovers we are using such 3552893Sdfr * hideous things. 3562893Sdfr */ 3572893Sdfr#undef isupper 3582893Sdfr#undef islower 3592893Sdfr#undef toupper 3602893Sdfr#undef tolower 361