127a0bc89SDoug Rabson /* $Id$ */ 227a0bc89SDoug Rabson /* $NetBSD: msdosfs_conv.c,v 1.6.2.1 1994/08/30 02:27:57 cgd Exp $ */ 327a0bc89SDoug Rabson 427a0bc89SDoug Rabson /* 527a0bc89SDoug Rabson * Written by Paul Popelka (paulp@uts.amdahl.com) 627a0bc89SDoug Rabson * 727a0bc89SDoug Rabson * You can do anything you want with this software, just don't say you wrote 827a0bc89SDoug Rabson * it, and don't remove this notice. 927a0bc89SDoug Rabson * 1027a0bc89SDoug Rabson * This software is provided "as is". 1127a0bc89SDoug Rabson * 1227a0bc89SDoug Rabson * The author supplies this software to be publicly redistributed on the 1327a0bc89SDoug Rabson * understanding that the author is not responsible for the correct 1427a0bc89SDoug Rabson * functioning of this software in any circumstances and is not liable for 1527a0bc89SDoug Rabson * any damages caused by this software. 1627a0bc89SDoug Rabson * 1727a0bc89SDoug Rabson * October 1992 1827a0bc89SDoug Rabson */ 1927a0bc89SDoug Rabson 2027a0bc89SDoug Rabson /* 2127a0bc89SDoug Rabson * System include files. 2227a0bc89SDoug Rabson */ 2327a0bc89SDoug Rabson #include <sys/param.h> 2427a0bc89SDoug Rabson #include <sys/time.h> 2527a0bc89SDoug Rabson #include <sys/kernel.h> /* defines tz */ 2627a0bc89SDoug Rabson 2727a0bc89SDoug Rabson /* 2827a0bc89SDoug Rabson * MSDOSFS include files. 2927a0bc89SDoug Rabson */ 3027a0bc89SDoug Rabson #include <msdosfs/direntry.h> 3127a0bc89SDoug Rabson 3227a0bc89SDoug Rabson /* 3327a0bc89SDoug Rabson * Days in each month in a regular year. 3427a0bc89SDoug Rabson */ 3527a0bc89SDoug Rabson u_short regyear[] = { 3627a0bc89SDoug Rabson 31, 28, 31, 30, 31, 30, 3727a0bc89SDoug Rabson 31, 31, 30, 31, 30, 31 3827a0bc89SDoug Rabson }; 3927a0bc89SDoug Rabson 4027a0bc89SDoug Rabson /* 4127a0bc89SDoug Rabson * Days in each month in a leap year. 4227a0bc89SDoug Rabson */ 4327a0bc89SDoug Rabson u_short leapyear[] = { 4427a0bc89SDoug Rabson 31, 29, 31, 30, 31, 30, 4527a0bc89SDoug Rabson 31, 31, 30, 31, 30, 31 4627a0bc89SDoug Rabson }; 4727a0bc89SDoug Rabson 4827a0bc89SDoug Rabson /* 4927a0bc89SDoug Rabson * Variables used to remember parts of the last time conversion. Maybe we 5027a0bc89SDoug Rabson * can avoid a full conversion. 5127a0bc89SDoug Rabson */ 5227a0bc89SDoug Rabson u_long lasttime; 5327a0bc89SDoug Rabson u_long lastday; 5427a0bc89SDoug Rabson u_short lastddate; 5527a0bc89SDoug Rabson u_short lastdtime; 5627a0bc89SDoug Rabson 5727a0bc89SDoug Rabson /* 5827a0bc89SDoug Rabson * Convert the unix version of time to dos's idea of time to be used in 5927a0bc89SDoug Rabson * file timestamps. The passed in unix time is assumed to be in GMT. 6027a0bc89SDoug Rabson */ 6127a0bc89SDoug Rabson void 6227a0bc89SDoug Rabson unix2dostime(tsp, ddp, dtp) 6327a0bc89SDoug Rabson struct timespec *tsp; 6427a0bc89SDoug Rabson u_short *ddp; 6527a0bc89SDoug Rabson u_short *dtp; 6627a0bc89SDoug Rabson { 6727a0bc89SDoug Rabson u_long t; 6827a0bc89SDoug Rabson u_long days; 6927a0bc89SDoug Rabson u_long inc; 7027a0bc89SDoug Rabson u_long year; 7127a0bc89SDoug Rabson u_long month; 7227a0bc89SDoug Rabson u_short *months; 7327a0bc89SDoug Rabson 7427a0bc89SDoug Rabson /* 7527a0bc89SDoug Rabson * If the time from the last conversion is the same as now, then 7627a0bc89SDoug Rabson * skip the computations and use the saved result. 7727a0bc89SDoug Rabson */ 7827a0bc89SDoug Rabson t = tsp->ts_sec - (tz.tz_minuteswest * 60) 7927a0bc89SDoug Rabson /* +- daylight savings time correction */ ; 8027a0bc89SDoug Rabson if (lasttime != t) { 8127a0bc89SDoug Rabson lasttime = t; 8227a0bc89SDoug Rabson lastdtime = (((t % 60) >> 1) << DT_2SECONDS_SHIFT) 8327a0bc89SDoug Rabson + (((t / 60) % 60) << DT_MINUTES_SHIFT) 8427a0bc89SDoug Rabson + (((t / 3600) % 24) << DT_HOURS_SHIFT); 8527a0bc89SDoug Rabson 8627a0bc89SDoug Rabson /* 8727a0bc89SDoug Rabson * If the number of days since 1970 is the same as the last 8827a0bc89SDoug Rabson * time we did the computation then skip all this leap year 8927a0bc89SDoug Rabson * and month stuff. 9027a0bc89SDoug Rabson */ 9127a0bc89SDoug Rabson days = t / (24 * 60 * 60); 9227a0bc89SDoug Rabson if (days != lastday) { 9327a0bc89SDoug Rabson lastday = days; 9427a0bc89SDoug Rabson for (year = 1970;; year++) { 9527a0bc89SDoug Rabson inc = year & 0x03 ? 365 : 366; 9627a0bc89SDoug Rabson if (days < inc) 9727a0bc89SDoug Rabson break; 9827a0bc89SDoug Rabson days -= inc; 9927a0bc89SDoug Rabson } 10027a0bc89SDoug Rabson months = year & 0x03 ? regyear : leapyear; 10127a0bc89SDoug Rabson for (month = 0; month < 12; month++) { 10227a0bc89SDoug Rabson if (days < months[month]) 10327a0bc89SDoug Rabson break; 10427a0bc89SDoug Rabson days -= months[month]; 10527a0bc89SDoug Rabson } 10627a0bc89SDoug Rabson lastddate = ((days + 1) << DD_DAY_SHIFT) 10727a0bc89SDoug Rabson + ((month + 1) << DD_MONTH_SHIFT); 10827a0bc89SDoug Rabson /* 10927a0bc89SDoug Rabson * Remember dos's idea of time is relative to 1980. 11027a0bc89SDoug Rabson * unix's is relative to 1970. If somehow we get a 11127a0bc89SDoug Rabson * time before 1980 then don't give totally crazy 11227a0bc89SDoug Rabson * results. 11327a0bc89SDoug Rabson */ 11427a0bc89SDoug Rabson if (year > 1980) 11527a0bc89SDoug Rabson lastddate += (year - 1980) << DD_YEAR_SHIFT; 11627a0bc89SDoug Rabson } 11727a0bc89SDoug Rabson } 11827a0bc89SDoug Rabson *dtp = lastdtime; 11927a0bc89SDoug Rabson *ddp = lastddate; 12027a0bc89SDoug Rabson } 12127a0bc89SDoug Rabson 12227a0bc89SDoug Rabson /* 12327a0bc89SDoug Rabson * The number of seconds between Jan 1, 1970 and Jan 1, 1980. In that 12427a0bc89SDoug Rabson * interval there were 8 regular years and 2 leap years. 12527a0bc89SDoug Rabson */ 12627a0bc89SDoug Rabson #define SECONDSTO1980 (((8 * 365) + (2 * 366)) * (24 * 60 * 60)) 12727a0bc89SDoug Rabson 12827a0bc89SDoug Rabson u_short lastdosdate; 12927a0bc89SDoug Rabson u_long lastseconds; 13027a0bc89SDoug Rabson 13127a0bc89SDoug Rabson /* 13227a0bc89SDoug Rabson * Convert from dos' idea of time to unix'. This will probably only be 13327a0bc89SDoug Rabson * called from the stat(), and fstat() system calls and so probably need 13427a0bc89SDoug Rabson * not be too efficient. 13527a0bc89SDoug Rabson */ 13627a0bc89SDoug Rabson void 13727a0bc89SDoug Rabson dos2unixtime(dd, dt, tsp) 13827a0bc89SDoug Rabson u_short dd; 13927a0bc89SDoug Rabson u_short dt; 14027a0bc89SDoug Rabson struct timespec *tsp; 14127a0bc89SDoug Rabson { 14227a0bc89SDoug Rabson u_long seconds; 14327a0bc89SDoug Rabson u_long m, month; 14427a0bc89SDoug Rabson u_long y, year; 14527a0bc89SDoug Rabson u_long days; 14627a0bc89SDoug Rabson u_short *months; 14727a0bc89SDoug Rabson 14827a0bc89SDoug Rabson seconds = ((dt & DT_2SECONDS_MASK) >> DT_2SECONDS_SHIFT) 14927a0bc89SDoug Rabson + ((dt & DT_MINUTES_MASK) >> DT_MINUTES_SHIFT) * 60 15027a0bc89SDoug Rabson + ((dt & DT_HOURS_MASK) >> DT_HOURS_SHIFT) * 3600; 15127a0bc89SDoug Rabson /* 15227a0bc89SDoug Rabson * If the year, month, and day from the last conversion are the 15327a0bc89SDoug Rabson * same then use the saved value. 15427a0bc89SDoug Rabson */ 15527a0bc89SDoug Rabson if (lastdosdate != dd) { 15627a0bc89SDoug Rabson lastdosdate = dd; 15727a0bc89SDoug Rabson days = 0; 15827a0bc89SDoug Rabson year = (dd & DD_YEAR_MASK) >> DD_YEAR_SHIFT; 15927a0bc89SDoug Rabson for (y = 0; y < year; y++) { 16027a0bc89SDoug Rabson days += y & 0x03 ? 365 : 366; 16127a0bc89SDoug Rabson } 16227a0bc89SDoug Rabson months = year & 0x03 ? regyear : leapyear; 16327a0bc89SDoug Rabson /* 16427a0bc89SDoug Rabson * Prevent going from 0 to 0xffffffff in the following 16527a0bc89SDoug Rabson * loop. 16627a0bc89SDoug Rabson */ 16727a0bc89SDoug Rabson month = (dd & DD_MONTH_MASK) >> DD_MONTH_SHIFT; 16827a0bc89SDoug Rabson if (month == 0) { 16927a0bc89SDoug Rabson printf("dos2unixtime(): month value out of range (%d)\n", 17027a0bc89SDoug Rabson month); 17127a0bc89SDoug Rabson month = 1; 17227a0bc89SDoug Rabson } 17327a0bc89SDoug Rabson for (m = 0; m < month - 1; m++) { 17427a0bc89SDoug Rabson days += months[m]; 17527a0bc89SDoug Rabson } 17627a0bc89SDoug Rabson days += ((dd & DD_DAY_MASK) >> DD_DAY_SHIFT) - 1; 17727a0bc89SDoug Rabson lastseconds = (days * 24 * 60 * 60) + SECONDSTO1980; 17827a0bc89SDoug Rabson } 17927a0bc89SDoug Rabson tsp->ts_sec = seconds + lastseconds + (tz.tz_minuteswest * 60) 18027a0bc89SDoug Rabson /* -+ daylight savings time correction */ ; 18127a0bc89SDoug Rabson tsp->ts_nsec = 0; 18227a0bc89SDoug Rabson } 18327a0bc89SDoug Rabson 18427a0bc89SDoug Rabson /* 18527a0bc89SDoug Rabson * Cheezy macros to do case detection and conversion for the ascii 18627a0bc89SDoug Rabson * character set. DOESN'T work for ebcdic. 18727a0bc89SDoug Rabson */ 18827a0bc89SDoug Rabson #define isupper(c) (c >= 'A' && c <= 'Z') 18927a0bc89SDoug Rabson #define islower(c) (c >= 'a' && c <= 'z') 19027a0bc89SDoug Rabson #define toupper(c) (c & ~' ') 19127a0bc89SDoug Rabson #define tolower(c) (c | ' ') 19227a0bc89SDoug Rabson 19327a0bc89SDoug Rabson /* 19427a0bc89SDoug Rabson * DOS filenames are made of 2 parts, the name part and the extension part. 19527a0bc89SDoug Rabson * The name part is 8 characters long and the extension part is 3 19627a0bc89SDoug Rabson * characters long. They may contain trailing blanks if the name or 19727a0bc89SDoug Rabson * extension are not long enough to fill their respective fields. 19827a0bc89SDoug Rabson */ 19927a0bc89SDoug Rabson 20027a0bc89SDoug Rabson /* 20127a0bc89SDoug Rabson * Convert a DOS filename to a unix filename. And, return the number of 20227a0bc89SDoug Rabson * characters in the resulting unix filename excluding the terminating 20327a0bc89SDoug Rabson * null. 20427a0bc89SDoug Rabson */ 20527a0bc89SDoug Rabson int 20627a0bc89SDoug Rabson dos2unixfn(dn, un) 20727a0bc89SDoug Rabson u_char dn[11]; 20827a0bc89SDoug Rabson u_char *un; 20927a0bc89SDoug Rabson { 21027a0bc89SDoug Rabson int i; 21127a0bc89SDoug Rabson int ni; 21227a0bc89SDoug Rabson int ei; 21327a0bc89SDoug Rabson int thislong = 0; 21427a0bc89SDoug Rabson u_char c; 21527a0bc89SDoug Rabson u_char *origun = un; 21627a0bc89SDoug Rabson 21727a0bc89SDoug Rabson /* 21827a0bc89SDoug Rabson * Find the last character in the name portion of the dos filename. 21927a0bc89SDoug Rabson */ 22027a0bc89SDoug Rabson for (ni = 7; ni >= 0; ni--) 22127a0bc89SDoug Rabson if (dn[ni] != ' ') 22227a0bc89SDoug Rabson break; 22327a0bc89SDoug Rabson 22427a0bc89SDoug Rabson /* 22527a0bc89SDoug Rabson * Find the last character in the extension portion of the 22627a0bc89SDoug Rabson * filename. 22727a0bc89SDoug Rabson */ 22827a0bc89SDoug Rabson for (ei = 10; ei >= 8; ei--) 22927a0bc89SDoug Rabson if (dn[ei] != ' ') 23027a0bc89SDoug Rabson break; 23127a0bc89SDoug Rabson 23227a0bc89SDoug Rabson /* 23327a0bc89SDoug Rabson * Copy the name portion into the unix filename string. NOTE: DOS 23427a0bc89SDoug Rabson * filenames are usually kept in upper case. To make it more unixy 23527a0bc89SDoug Rabson * we convert all DOS filenames to lower case. Some may like this, 23627a0bc89SDoug Rabson * some may not. 23727a0bc89SDoug Rabson */ 23827a0bc89SDoug Rabson for (i = 0; i <= ni; i++) { 23927a0bc89SDoug Rabson c = dn[i]; 24027a0bc89SDoug Rabson *un++ = isupper(c) ? tolower(c) : c; 24127a0bc89SDoug Rabson thislong++; 24227a0bc89SDoug Rabson } 24327a0bc89SDoug Rabson 24427a0bc89SDoug Rabson /* 24527a0bc89SDoug Rabson * Now, if there is an extension then put in a period and copy in 24627a0bc89SDoug Rabson * the extension. 24727a0bc89SDoug Rabson */ 24827a0bc89SDoug Rabson if (ei >= 8) { 24927a0bc89SDoug Rabson *un++ = '.'; 25027a0bc89SDoug Rabson thislong++; 25127a0bc89SDoug Rabson for (i = 8; i <= ei; i++) { 25227a0bc89SDoug Rabson c = dn[i]; 25327a0bc89SDoug Rabson *un++ = isupper(c) ? tolower(c) : c; 25427a0bc89SDoug Rabson thislong++; 25527a0bc89SDoug Rabson } 25627a0bc89SDoug Rabson } 25727a0bc89SDoug Rabson *un++ = 0; 25827a0bc89SDoug Rabson 25927a0bc89SDoug Rabson /* 26027a0bc89SDoug Rabson * If first char of the filename is SLOT_E5 (0x05), then the real 26127a0bc89SDoug Rabson * first char of the filename should be 0xe5. But, they couldn't 26227a0bc89SDoug Rabson * just have a 0xe5 mean 0xe5 because that is used to mean a freed 26327a0bc89SDoug Rabson * directory slot. Another dos quirk. 26427a0bc89SDoug Rabson */ 26527a0bc89SDoug Rabson if (*origun == SLOT_E5) 26627a0bc89SDoug Rabson *origun = 0xe5; 26727a0bc89SDoug Rabson 26827a0bc89SDoug Rabson return thislong; 26927a0bc89SDoug Rabson } 27027a0bc89SDoug Rabson 27127a0bc89SDoug Rabson /* 27227a0bc89SDoug Rabson * Convert a unix filename to a DOS filename. This function does not ensure 27327a0bc89SDoug Rabson * that valid characters for a dos filename are supplied. 27427a0bc89SDoug Rabson */ 27527a0bc89SDoug Rabson void 27627a0bc89SDoug Rabson unix2dosfn(un, dn, unlen) 27727a0bc89SDoug Rabson u_char *un; 27827a0bc89SDoug Rabson u_char dn[11]; 27927a0bc89SDoug Rabson int unlen; 28027a0bc89SDoug Rabson { 28127a0bc89SDoug Rabson int i; 28227a0bc89SDoug Rabson u_char c; 28327a0bc89SDoug Rabson 28427a0bc89SDoug Rabson /* 28527a0bc89SDoug Rabson * Fill the dos filename string with blanks. These are DOS's pad 28627a0bc89SDoug Rabson * characters. 28727a0bc89SDoug Rabson */ 28827a0bc89SDoug Rabson for (i = 0; i <= 10; i++) 28927a0bc89SDoug Rabson dn[i] = ' '; 29027a0bc89SDoug Rabson 29127a0bc89SDoug Rabson /* 29227a0bc89SDoug Rabson * The filenames "." and ".." are handled specially, since they 29327a0bc89SDoug Rabson * don't follow dos filename rules. 29427a0bc89SDoug Rabson */ 29527a0bc89SDoug Rabson if (un[0] == '.' && unlen == 1) { 29627a0bc89SDoug Rabson dn[0] = '.'; 29727a0bc89SDoug Rabson return; 29827a0bc89SDoug Rabson } 29927a0bc89SDoug Rabson if (un[0] == '.' && un[1] == '.' && unlen == 2) { 30027a0bc89SDoug Rabson dn[0] = '.'; 30127a0bc89SDoug Rabson dn[1] = '.'; 30227a0bc89SDoug Rabson return; 30327a0bc89SDoug Rabson } 30427a0bc89SDoug Rabson 30527a0bc89SDoug Rabson /* 30627a0bc89SDoug Rabson * Copy the unix filename into the dos filename string upto the end 30727a0bc89SDoug Rabson * of string, a '.', or 8 characters. Whichever happens first stops 30827a0bc89SDoug Rabson * us. This forms the name portion of the dos filename. Fold to 30927a0bc89SDoug Rabson * upper case. 31027a0bc89SDoug Rabson */ 31127a0bc89SDoug Rabson for (i = 0; i <= 7 && unlen && (c = *un) && c != '.'; i++) { 31227a0bc89SDoug Rabson dn[i] = islower(c) ? toupper(c) : c; 31327a0bc89SDoug Rabson un++; 31427a0bc89SDoug Rabson unlen--; 31527a0bc89SDoug Rabson } 31627a0bc89SDoug Rabson 31727a0bc89SDoug Rabson /* 31827a0bc89SDoug Rabson * If the first char of the filename is 0xe5, then translate it to 31927a0bc89SDoug Rabson * 0x05. This is because 0xe5 is the marker for a deleted 32027a0bc89SDoug Rabson * directory slot. I guess this means you can't have filenames 32127a0bc89SDoug Rabson * that start with 0x05. I suppose we should check for this and 32227a0bc89SDoug Rabson * doing something about it. 32327a0bc89SDoug Rabson */ 32427a0bc89SDoug Rabson if (dn[0] == SLOT_DELETED) 32527a0bc89SDoug Rabson dn[0] = SLOT_E5; 32627a0bc89SDoug Rabson 32727a0bc89SDoug Rabson /* 32827a0bc89SDoug Rabson * Strip any further characters up to a '.' or the end of the 32927a0bc89SDoug Rabson * string. 33027a0bc89SDoug Rabson */ 33127a0bc89SDoug Rabson while (unlen && (c = *un)) { 33227a0bc89SDoug Rabson un++; 33327a0bc89SDoug Rabson unlen--; 33427a0bc89SDoug Rabson /* Make sure we've skipped over the dot before stopping. */ 33527a0bc89SDoug Rabson if (c == '.') 33627a0bc89SDoug Rabson break; 33727a0bc89SDoug Rabson } 33827a0bc89SDoug Rabson 33927a0bc89SDoug Rabson /* 34027a0bc89SDoug Rabson * Copy in the extension part of the name, if any. Force to upper 34127a0bc89SDoug Rabson * case. Note that the extension is allowed to contain '.'s. 34227a0bc89SDoug Rabson * Filenames in this form are probably inaccessable under dos. 34327a0bc89SDoug Rabson */ 34427a0bc89SDoug Rabson for (i = 8; i <= 10 && unlen && (c = *un); i++) { 34527a0bc89SDoug Rabson dn[i] = islower(c) ? toupper(c) : c; 34627a0bc89SDoug Rabson un++; 34727a0bc89SDoug Rabson unlen--; 34827a0bc89SDoug Rabson } 34927a0bc89SDoug Rabson } 35027a0bc89SDoug Rabson 35127a0bc89SDoug Rabson /* 35227a0bc89SDoug Rabson * Get rid of these macros before someone discovers we are using such 35327a0bc89SDoug Rabson * hideous things. 35427a0bc89SDoug Rabson */ 35527a0bc89SDoug Rabson #undef isupper 35627a0bc89SDoug Rabson #undef islower 35727a0bc89SDoug Rabson #undef toupper 35827a0bc89SDoug Rabson #undef tolower 359